Wednesday, June 16, 2010

Powershell script to check for pending reboots (specifically for WSUS / Automatic Updates pending reboots)

I've been looking for a reliable way to determine if servers I've pushed patches to via WSUS (we use a manually-activated scheduled tasks to forcibly apply pending patches) have triggered a pending reboot.  I did a quick google search and found a script by a gentleman named Moe Winnett over at http://www.techmumbojumblog.com/?p=375&cpage=1 that does 99% of what I needed.  It didn't include the paths needed for WSUS/Automatic Updates pending reboots so I added that (and sent them the additions).

This is rough, and provided in case it helps anyone.  It actually will be part of a larger management script at some point.


$Version = "wsus_rbquery.ps1 v10.6.14 Aaron Dodd"
$Usage = "Usage: wsus_rbquery.ps1 [servers], where [servers] is one or more servers to set up the tasks on (comma-separated if more than one) or a one-server-per line text file ending in .txt"
$Description = "Checks for pending reboots on target server(s)"

# Based on a script found at http://www.techmumbojumblog.com/?p=375&cpage=1

# Expects 1 or more (comma-delimited if more) servers
If (!$Args[0]) {
    Write-Host $Version
    Write-Host $Usage
    Write-Host $Description
    Exit
}

$Servers = $Args[0].ToLower()

If ($Servers.Contains(".txt")) {
    $Servers = gc $Servers 
} ElseIf ($Servers.Contains(",")) {
    $Servers = [regex]::Split($Servers,",")
}


function Get-PendingReboot {
    param([string]$computer)
 $hkey  = 'LocalMachine';
 $path_server = 'SOFTWARE\Microsoft\ServerManager';
    $path_wsus      = 'SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired';
 $path_control = 'SYSTEM\CurrentControlSet\Control';
 $path_session = join-path $path_control 'Session Manager';
 $path_name = join-path $path_control 'ComputerName';
 $path_name_old = join-path $path_name 'ActiveComputerName';
 $path_name_new = join-path $path_name 'ComputerName';

 $pending_rename = 'PendingFileRenameOperations';
 $pending_rename_2 = 'PendingFileRenameOperations2';
 $attempts = 'CurrentRebootAttempts';
 $computer_name = 'ComputerName';

 $num_attempts = 0;
 $name_old = $null;
 $name_new = $null;

 $reg= [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($hkey, $computer);

 $key_session = $reg.OpenSubKey($path_session);
 if ($key_session -ne $null) {
  $session_values = @($key_session.GetValueNames());
        $key_session.Close() | out-null;
 }

 $key_server = $reg.OpenSubKey($path_server);
 if ($key_server -ne $null) {
  $num_attempts = $key_server.GetValue($attempts);
  $key_server.Close() | out-null;
 }

 $key_name_old = $reg.OpenSubKey($path_name_old);
 if ($key_name_old -ne $null) {
  $name_old = $key_name_old.GetValue($computer_name);
  $key_name_old.Close() | out-null;

  $key_name_new = $reg.OpenSubKey($path_name_new);
  if ($key_name_new -ne $null) {
   $name_new = $key_name_new.GetValue($computer_name);
   $key_name_new.Close() | out-null;
  }
 }
    
    $key_wsus = $reg.OpenSubKey($path_wsus);
 if ($key_wsus -ne $null) {
  $wsus_values = @($key_wsus.GetValueNames());
        if ($wsus_values) { 
            $wsus_rbpending = $true 
        } else {
            $wsus_rbpending = $false
        }
  $key_wsus.Close() | out-null;
 }

 $reg.Close() | out-null;

 if ( `
        (($session_values -contains $pending_rename) -or ($session_values -contains $pending_rename_2)) `
 -or (($num_attempts -gt 0) -or ($name_old -ne $name_new)) `
    -or ($wsus_rbpending)) {
  return $true;
 }
 else {
  return $false;
 }
}

ForEach ($Server in $servers) {
    Write-Host $Server - (Get-PendingReboot $Server)
}

Tuesday, December 8, 2009

Importing Windows-based Outlook tasks into Mozilla Thunderbird/Lightning/Sunbird on OS X

I usually track all work as Outlook tasks and keep work requests as the body/description of the task.  Its not the best workflow, but its easy and allows for the mostly-email-based requests to be quickly tagged, given a reminder/due date, and tracked.

I decided to switch to Thunderbird for a variety of reasons, and the one thing I was really missing was how to get my tasks from Outlook into Thunderbird/Lightning.  The calendars were no problem as I have Outlook using the GoogleSync app from Google to keep that calendar in sync.  But tasks aren't supported that way.

However, after way too much digging and surfing and tinkering, I realized that since I'm using OS X, specifically Snow Leopard, and it supports Exchange sync, the answers were built into my OS.

To get my tasks from Outlook into Thunderbird I did the following:
  • I set up Apple's iCal to use our corporate Exchange2007 server for calendar and tasks
  • Once that was up, I went to the Finder and looked under my home folder, Library, Calendars
  • There were a couple of folders with long GUID names (long numbers/letters separated by dashes) ending in ".calendar".  Expanding this showed a folder called "Events" which contained one .ics file for every task that was sync'd from Exchange
  • I copied all of these .ics files to a temporary folder
  • This step is optional, but I recommend it otherwise you're going to repeat the import steps for each .ics file: I then opened the terminal, changed to the folder with the .ics files, and typed "cat *.ics > merged.ics".  This took each of the individual .ics files and made them into one.
  • In Thunderbird I went to the Calendar page, clicked "File / Import Calendar" and browsed to the "merged.ics" file above
This didn't preserve file attachments (which I didn't expect to be preserved) or categories, but everything else seems to be in tact.

I wish I knew of a more cross-platform solution, but since I use OS X this worked fine for me ;-)

Saturday, October 10, 2009

Powershell script to change local administrator password across multiple computers

Ok, this is yet another script for changing local administrator passwords across most Windows servers and desktops (works on Windows 2000, 2003, 2008, XP, and Vista). The difference here is in my quest to find one quickly, I stumbled across a bunch of VBScripts that are decent but overkill, so I ended up writing my own script that will:
  1. Parse a list of server names

  2. Verify the server is online

  3. Only try to change the password if it is online

  4. Tell me if it succeeded, failed, or skipped a down system



Fortunately, all of this is available in Powershell with little real effort. Below is a quick script to do all of the above. Before running it, be sure that:
  1. The account you're running it under has administrative rights to each system. If you're spanning domains without trusts, you should use the "Stored Usernames and Passwords" control panel to add your credentials for each domain (specify the resource as *.domain.name, and use FQDN's in step 2).

  2. You create a list of servers, one per line, called "serverlist.txt" in the same folder from which you run the script (you can change the filename and path by editing the script below).

  3. You change the placeholders below to have the proper local admin username and new password (we have a GPO to rename the administrator account, so I don't assume "Administrator")


The script:
$erroractionpreference = "SilentlyContinue"
foreach ($Computer in get-content serverlist.txt) {
$ServerName = $Computer.ToUpper()

$ping = new-object System.Net.NetworkInformation.Ping
$Reply = $ping.send($Computer)

if($Reply.status -eq "success") {
Write-Host "$ServerName is online"
$Admin=[adsi]("WinNT://" + $Computer + "/--ADMINUSERNAMEHERE--, user")
$Admin.PSBase.Invoke("SetPassword", "--NEWPASSWORDHERE--")

# Verify password was just changed
$PasswordAge = $Admin.PasswordAge
If($PasswordAge -ne $null) {
Write-Host "$ServerName password change SUCCEEDED"
} Else {
Write-Host "$ServerName password change FAILED"
}
} Else {
Write-Host "$ServerName is not online - skipping"
}
}

Tuesday, September 1, 2009

LogParser Lizard on Windows Vista x64, 2003 x64, 2008 x64

The insanely useful "LogParser Lizard" from LizardLabs is something I use daily to parse logs across our server farms (i.e. did that email from someluser@somecompany.com actually leave one of the dozen front end mail servers?  Did we get any FTP error code 426's for the past week anywhere on the FTP server farm?).  It's a front-end to Microsoft's LogParser.exe so it reads pretty much any log format used on Windows, plus some niceties like AD querying, SQL DB querying, and making pretty charts out of the results.



The issue is the server I run it from is Windows 2003 32-bit.  When I tried to run it from our new management server, 2008 x64, or from any of the 64-bit 2003 servers, it would seem to start, then crash.  I'd see the following error in the event log:


Faulting application log parser lizard.exe, version 1.0.0.0, stamp 48565da6, faulting module kernel32.dll, version 5.2.3790.4062, stamp 462643a7, debug? 0, fault address 0x000000000000dd10.

I've seen this before, actually, on our own code.  Seems sometimes developers will write an app in a 32-bit environment but not explicitly set it to load 32-bit.  This is generally ok since most .NET apps will run fine in 32-bit or 64-bit, but it seems LogParser Lizard includes a lot of freeware .dll's that are 32-bit only.  As a result, even though the main .exe loads 64-bit, it tries to then load 32-bit only .dll's.

Fortunately this is easy to fix.  If you don't already have it installed, install the Microsoft .NET 2.0 SDK.  In it there's a tool called "corflags.exe" (no, there's no "e" in corflag).  This is used to modify the header of a binary to set it to be explicitly 32-bit.

To do this, run:

corflags.exe c:\full\path\to\logparserlizard.exe /32BIT+

To undo it, run the same with /32BIT-

Then it runs fine :-)

Monday, April 27, 2009

A Script to fix Windows Update Services clients not appearing in the WSUS console

After going through a bunch of patching solutions and being somewhat dissatisfied with most, I decided to give Microsoft's Windows Update Services another go. I hadn't used it since the 1.0 days and it was, well, rather 1.0 then.

WSUS 3.0 has a decent feature set, the two most desirable ones being a nice reporting system and the fact it's client is built into Windows (yes yes we can go back and forth on the merits or evil-ness of this fact, but its built in and therefore one less client I need to install...)

In our lab, this worked great. I have a setup where our load-balanced farms update and reboot several hours apart on saturdays, and our core boxes download but wait for manual updating. It was, in fact, one of the most flawless, hands-off setups I've worked with.

However, this was in the lab. In the real world, I ran into several issues. If you google for "WSUS clients not registering" or "WSUS clients disappear" you'll see a myriad of issues, several of which I came across. I won't post them here but the one that really irked me was this:

In production we have several hundred machines, most of which all show up in the WSUS console just fine. However, a few dozen were not appearing. There seemed to be no real commonality between them. They were a mix of 32-bit 2003, 64-bit 2003, and 64-bit 2008, some the "R2" editions of each. MS's "clientdiag.exe" tool passed on all. All the MS troubleshooting steps also confirmed access to the WSUS server. The "WindowsUpdate.log" showed no errors beyond normal transient ones.

Turns out, after much digging, I stumbled across this registry key:
hklm\software\microsoft\windows\currentversion\windowsupdate\susclientid

This, it seems, was the commonality of the several dozen machines. Each set of machines having issues (32-bit 2003 boxes, 64-bot 2003 boxes, 2008 boxes) had the same value as each other of the same time. Some investigation revealed these servers were clones of each other. While the technician that built them ran NewSID or sysprep'd them, this key didn't change. As a result, they'd check in to WSUS and conflict.

Now, this was only one of several issues I found. It seems every so often I bring a new box into the WSUS system to find it not working right away. I wrote the following script which so far addresses all of the issues I've come across. It works on both 32-bit and 64-bit 2003 and 2008:

@echo off
::Version 9.4.26 Aaron Dodd

echo Stopping Windows Update Services
net stop wuauserv >NUL

echo Deleting existing WSUS registry keys
:: This deletes the WSUS keys managed by GPO
reg delete hklm\software\policies\microsoft\windows\windowsupdate /f >NUL

:: This deletes WSUS ID's that should be different but can be the same if windows was installed via an image
reg delete HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate /v AccountDomainSid /f >NUL
reg delete HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate /v PingID /f >NUL
reg delete HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate /v SusClientId /f >NUL

echo Forcing policy update to restore registry keys
gpupdate /target:computer /force >NUL

echo Deleting existing local WSUS repository
del /s /q c:\windows\softwaredistribution >NUL
if exist c:\windows\syswow64 del /s /q c:\windows\syswow64\softwaredistribution >NUL

Echo Restarting BITS
net stop bits >NUL
net start bits >NUL

Echo Starting Windows Update Services
net stop wuauserv >NUL
net start wuauserv >NUL

Echo Forcing re-authorization to WSUS server and detection of patches
wuauclt /resetauthorization /detectnow >NUL

So far I haven't found a WSUS issue it hasn't fixed for me.

Note: this script does assume you're deploying WSUS settings via GPO. As a result, its safe to delete the entire policies...windowsupdate subkey because the GPO replaces it. Also, deleting "softwaredistribution" is safe because wuauserv ("Windows Update" service) recreates it.

The BITS part I'm not sure is necessary, but can't hurt ;-)

Also, yes, I'm stopping wuauserv again before starting it. It seems the GPO update sometimes starts it and since we just deleted the "softwaredistribution" folder, its necessary to stop/start the service again to rebuild it.

If you want to deploy this to many machines at once, I suggest using psexec.exe with the "-c" flag to copy this script and execute it on the target systems. I.e. save the script as "fix_wsus.cmd":

for %y in (svr1,svr2,svr3) do psexec.exe \\%y -c fix_wsus.cmd
See "for /?" for how to specify a text file of server names if you have a lot.

Monday, March 30, 2009

Simple means of syncing IIS7 settings in a webfarm

Here's our issue:

We have dozens of IIS servers in our webfarms. Each webfarm handles specific sets of applications. All members of each webfarm are supposed to be identical.

With IIS6 this was really simple. We would:
  1. Remove one node from the webfarm
  2. Make IIS changes on one node
  3. Robocopy the code to that node
  4. Test against that node
If successful we would:
  1. Bring that node back and take out a second node
  2. robocopy code to that node
  3. run "cscript c:\windows\system32\iiscnfg.vbs /copy /ts \\targetserver /tu domain\user /tp password" to sync the metabase to that second nodebring that node back into the webfarm
  4. repeat for other nodes
This may sound like a lot of steps but I'm simply writing out what our script does for us automatically.

With IIS7, we could keep the same process if we installed the IIS6 metabase compatibility. But, I'm not one for holding onto the past without a really good reason and while this functionality seems like a good reason, I'd rather have a more supported method that uses today's functionality.

Microsoft's management for IIS7 is . . . interesting in my opinion. They make an excellent step towards scriptable management by putting everything in .xml files instead of binary metabase. You can read these settings in c:\windows\system32\inetsrv\config\ApplicationHost.config. The "interesting" part is that Microsoft for some reason decided to keep machine specific settings in this file (notably, the encryption keys). This means if you simply copy this applicationHost.config to a sister server, you'll find IIS won't even start.

Okay, so what about scripting? Well, Microsoft no longer includes iiscnfg.vbs (unless you include IIS6 compatability). They have the rather nice appcmd.exe tool, but it doesn't work against remote systems. They have the "Web Deployment Tool" but that requires re-packaging your application.

I decided instead we should standardize on as little customization as possible, so Web Deployment Tool is out unless our developers will be sending us packaged deployments (which I think is a terrible idea for a production webfarm as you lose control over your server to whatever development thinks are appropriate settings). Instead, we opted on a slight bastardization of Microsoft's official webfarm setup.

You'll want to read Microsoft's IIS7 Shared Configuration guide for more details, especially for the nice screen shots. We followed those with one exception: We are using a local folder for the "central UNC path" instead.

This gives us two advantages over Microsoft's shared configuration recommendation:
  1. Immunity from issues with a UNC share (credentials changing, share going down, etc)
  2. The ability to make changes to just one node for testing before synchronizing, or for troubleshooting
It was really the second advantage that made us go this route as a pre-flight test is required prior to making a change live.

Since all of our code goes to D:\applications\[appname] which we use Robocopy to sync to all sister nodes, we just created a "D:\applications\iis_settings" folder and set that as the shared configuration path. Now, when we robocopy our code, the IIS7 settings get immediately replicated to the sister nodes as well. Our original process is actually a step shorter.

Saturday, February 14, 2009

Importing Apple Mail into KDE's KMail

There's a ton of articles on teh interwebz on how to import mail into Apple's Mail.app from KDE's KMail, but I hadn't come across anything on doing the reverse.

It turns out this is insanely simple, just not obvious, especially since it wouldn't work the "official" way.

KMail has some wonderful importers, including one called "Import from OS X Mail". Being the painfully obvious choice, I fired up KMail, clicked "File / Import Messages", chose "Import from OS X Mail" and pointed it to my Mac's Library/Mail/Mailboxes folder. I tried the root level, the mailbox folder itself, then even the subfolder "messages" which contained the actual emails. It never actually worked.

If I ever care to figure out why, I might look into it more, but the following seems to be a foolproof and quick way.

In OS X's Mail.app you can right-click a mail folder and choose "Archive". This seems mis-named to me, since generally I consider "archiving" email to mean you're sending it to an archive and removing it from the folder. Really, this is more of a "backup". Anyway, I digress...

1. Right-click the folder you want to send to KMail
2. Choose "Archive"
3. Point it to a folder. This will create a subfolder with the name of this Mailbox, which will then contain all of the email in an MBOX file.
4. Open KMail
5. Choose "File / Import Messages", choose "Import MBox files"
6. Point it to the mbox file itself that Apple Mail saved.

Voila!