Windows Server Updates

This is what happens when automatic updates are turned off and I’m left to update the servers….I find a way to do it with as little interaction as possible.

Cracking the Windows Update API

Something that I’ve been working on for a while is an application or script that checks the availability of other servers to decide if it should start the update/restart steps. Though testing and more testing, I’ve found that even with the awesome power of Powershell, I still needed the power of a binary to get the best performance and least complexity.

However, unlike with Powershell where you can just set a variable, you gotta put more effort with C# to do something similar, especially when there’s so little documentation and I know so little about nitty gritty COM programming.

So getting the WUApi working in C# things to know.

  1. You need to reference “c:\windows\system32\wuapi.dll”
  2. You must have have the resulting “Interop.WUApi.DLL” in the same directory as you executable. (I haven’t figured out a way past this yet.)
  3. WUApi likes to die and quickly if there is the slightest error, and under C# these errors seem to be harder to pick up on.
  4. Some updates don’t like to report back how far they are in their process. IE8 for example sits at 0% until its done, so you just have to wait and wait some more.

The following code you can pretty much copy and paste into a cs file add the necessary hooks into the blank C# project, compile and it should work. It’s very Alpha and there is little error checking since I haven’t taken the time to throw test cases its way. It should also work on any OS from XP to 7.

namespace winupdate_scheduler
{
    /// <remarks>Methods actual shutdown.</remarks>
    public class grunt
    {
        public void shutdown()
        {
            throw new System.NotImplementedException();
        }

        public void restart()
        {
            throw new System.NotImplementedException();
        }

        /// <summary>
        /// Initiate the update for great justice!
        /// </summary>
        public void update()
        {
            try
            {
                //Initate the session and begin the search!
                WUApiLib.UpdateSessionClass session = new WUApiLib.UpdateSessionClass();
                WUApiLib.IUpdateSearcher searcher = session.CreateUpdateSearcher();
                WUApiLib.ISearchResult result = searcher.Search("IsInstalled=0 and IsAssigned=1");

                //Initate the interfaces for progress and wholeness.
                InstallProgress progress = new InstallProgress();
                InstallComplete completion = new InstallComplete();

                //Dummy object for installer.BeginInstall
                object t3 = (new object());

                //Do we have anything to do?
                if (result.Updates.Count > 0)
                {
                    //Begin the long wait.
                    //Probably should look into doing that async thing here too....later.
                    Console.WriteLine("Downloading...");
                    WUApiLib.IUpdateDownloader download = session.CreateUpdateDownloader();
                    download.Updates = result.Updates;
                    download.Download();

                    //Finally, get around to installing the suckers!
                    Console.WriteLine("Installing...");
                    WUApiLib.IUpdateInstaller installer = session.CreateUpdateInstaller();
                    installer.Updates = download.Updates;
                    installer.BeginInstall(progress, completion, t3);

                    //Hold your horses! We're not done yet!
                    while (!completion.jobdone)
                    {
                        System.Threading.Thread.Sleep(2000);
                    }
                }

                //Now you can let go of your horses!
                Console.WriteLine("Done");
            }
                //Until I can get a grasp of all the exceptions that can be raised, this will have to do.
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        /// <summary>
        /// Get list of updates
        /// </summary>
        public void getupdates()
        {
            throw new System.NotImplementedException();
            WUApiLib.UpdateSessionClass session = new WUApiLib.UpdateSessionClass();
            WUApiLib.IUpdateSearcher searcher = session.CreateUpdateSearcher();
            WUApiLib.ISearchResult result = searcher.Search("IsInstalled=0 and IsAssigned=1");
        }
    }

    class InstallProgress : WUApiLib.IInstallationProgressChangedCallback
    {

        public void Invoke(WUApiLib.IInstallationJob job, WUApiLib.IInstallationProgressChangedCallbackArgs args)
        {
            this.job = job;
            this.OverallProgress = this.job.GetProgress().PercentComplete;
            this.UpdateProgress = this.job.GetProgress().CurrentUpdatePercentComplete;
            this.UpdateIndex = this.job.GetProgress().CurrentUpdateIndex;

            this.Update = this.job.Updates[this.UpdateIndex];

            string str = "Update: {0}, UpdateProgress: {1}%, Progress {2}%";
            Console.WriteLine(string.Format(str, (this.UpdateIndex + 1), this.UpdateProgress, this.OverallProgress));
        }

        public WUApiLib.IInstallationJob job;

        public int OverallProgress = 0;
        public int UpdateProgress = 0;
        public int UpdateIndex = 0;
        public WUApiLib.IUpdate Update;

    }

    class InstallComplete : WUApiLib.IInstallationCompletedCallback
    {
        public void Invoke(WUApiLib.IInstallationJob job, WUApiLib.IInstallationCompletedCallbackArgs args)
        {
            //string str = "job.IsCompleted: {0}, args.Progress: {1}% completed";
            //Console.WriteLine(string.Format(str, job.IsCompleted, job.GetProgress().PercentComplete));
            this.jobdone = job.IsCompleted;
        }
        public bool jobdone = false;
    }
}

More reading:

Fixed: fastping, now with working get-help comments

After getting frustrated with my fastping not displaying anything after using get-help, I asked the twitterverse for help, and they did! Super-duper thanks to @djryan (twitter/web) for the pointers.

<#
.SYNOPSIS
Ping a node as fast as you can.
.DESCRIPTION
Whereas the ping.exe utility keeps steady at 1 ping per second,
this will ping with custom wait periods as low as 0 milliseconds.
.Parameter ip
IP or hostname of network node.
.Parameter count
Number of pings to execute (defaults to 4)
.Parameter wait
Time to wait for a reply.
.Parameter sleep
Time to wait between pings.
.Parameter size
Number of bytes to send (all Null or 0).
.Parameter ttl
Time to live.
.INPUTS
None. You cannot pipe objects to fastping.
.OUTPUTS
Reply from {host}: status={status bytes={bytes} time={tripTime}
(xxx% loss)
.Example
    PS> fastping.ps1 -ip josherickson.org -size 1337 -wait 1
	Reply from josherickson.org: status=Success bytes=1337 time=81
	Reply from josherickson.org: status=Success bytes=1337 time=96
	Reply from josherickson.org: status=Success bytes=1337 time=81
	Reply from josherickson.org: status=TimedOut bytes=1337 time=0
	(25% loss)
.LINK
Original: http://josherickson.org/2009/09/24/586/ping-faster-from-powershell-with-fastping
.Notes
TODO: Add object based return instead of just text strings.
NAME:      verb
AUTHOR:    josherickson.org\josh
LASTEDIT:  09/25/2009
#>
param (
	[string]$ip = "127.0.0.1",
	[int]$count = 4,
	[int]$wait = 5,
	[int]$sleep = 0,
	[int]$size = 32,
	[int]$ttl = 128
)

$ping = new-object System.Net.NetworkInformation.ping
$pingo = new-object System.Net.NetworkInformation.PingOptions $ttl, $false

if($size -gt 65500) {
	throw "`$size must be equal to or less than 65500";
}

$bsize = new-object byte[] $size;

$s = 0;
$f = 0;
for($i = 0; $i -lt $count; $i++) {
	$pr = $ping.Send($ip, $wait, $bsize, $pingo)
	$s += [int][bool]($pr.status -eq "Success");
	$f += [int][bool]($pr.status -ne "Success");

	"Reply from $($ip): status=$($pr.status) bytes=$size time=$($pr.RoundtripTime)" | write-host

	sleep -Milliseconds $sleep;
}

if($f -gt 0) {
	"(" + ((100/$count)*$f) + "% loss)";
} else {
	"(0% loss)";
}
return;

Ping faster from powershell with FastPing!

I’ll let the code do most of the talking.

<#
.Synopsis
    Ping a node as fast as you can.
.Description
    Whereas the ping.exe utility keeps steady at 1 ping per second,
	this will ping with custom wait periods as low as 0 milliseconds.
.Parameter ip
	IP or hostname of network node.
.Parameter count
	Number of pings to execute (defaults to 4)
.Parameter wait
	Time to wait for a reply.
.Parameter sleep
	Time to wait between pings.
.Parameter size
	Number of bytes to send (all Null or 0).
.Parameter ttl
	Time to live.
.Example
    PS> fastping.ps1 -ip josherickson.org -size 1337 -wait 1
	Reply from josherickson.org: status=Success bytes=1337 time=81
	Reply from josherickson.org: status=Success bytes=1337 time=96
	Reply from josherickson.org: status=Success bytes=1337 time=81
	Reply from josherickson.org: status=TimedOut bytes=1337 time=0
	(25% loss)

.ReturnValue
    [string]

.Link
.Notes
	TODO: Add object based return instead of just text strings.
NAME:      verb
AUTHOR:    josherickson.org\josh
LASTEDIT:  09/24/2009
#Requires -Version 2.0
#>
param (
	[string]$ip = "127.0.0.1",
	[int]$count = 4,
	[int]$wait = 5,
	[int]$sleep = 0,
	[int]$size = 32,
	[int]$ttl = 128
)

$ping = new-object System.Net.NetworkInformation.ping
$pingo = new-object System.Net.NetworkInformation.PingOptions $ttl, $false

if($size -gt 65500) {
	throw "`$size must be equal to or less than 65500";
}

$bsize = new-object byte[] $size;

$s = 0;
$f = 0;
for($i = 0; $i -lt $count; $i++) {
	$pr = $ping.Send($ip, $wait, $bsize, $pingo)
	$s += [int][bool]($pr.status -eq "Success");
	$f += [int][bool]($pr.status -ne "Success");

	"Reply from $($ip): status=$($pr.status) bytes=$size time=$($pr.RoundtripTime)"

	sleep -Milliseconds $sleep;
}

if($f -gt 0) {
	"(" + ((100/$count)*$f) + "% loss)";
} else {
	"(0% loss)";
}

Note that the time it takes to ping a place reflects the round trip time, which is how long it takes to get the entire reply, that is why you can see times greater than the specified wait time.

Finding applicable Windows Updates via cli

An ongoing project here at work is server maintenance and applying patches. One of the issue we’ve been facing is how to do windows updates for clusters. While we could do an AD structure and schedule different install and restart times for the different sides, we can’t include checks to see if the other side of the cluster is up.

So now, we’re working on a set of tools that can initiate Windows Updates, check if other servers are up or down, and then restart accordingly. Thus far, I’ve just finished this script, the rest of the tool will likely be done with C# and set as a service on each machine. With this script, you can check the updates for other machines from the comfort of your desktop/laptop and send it off to a text file for later viewing. You can even initiate the update process! Albeit, only on the machine you’re running on due to constraints of the Microsoft.Update.Session COM.

param (
	$computer = $env:computername,
	[switch]$searchOnly
)
[Reflection.Assembly]::LoadFrom("C:\WINDOWS\Microsoft.NET\Framework64\v2.0.50727\Microsoft.VisualBasic.dll") | out-null;

#Create an update session
#Using the vb createObject method, we are able to connect to remote machines to view
#it's update list
$updateSession = [microsoft.visualbasic.interaction]::CreateObject("Microsoft.Update.Session", $computer);

"Finding updates on $($computer)"
$updateSearch = $updateSession.CreateUpdateSearcher()
#Here we're only wanting to know what it needs and what it has been assigned.
#IsAssigned should really only apply to networks that utilize WSUS.

$results = $updateSearch.Search('IsInstalled=0 and IsAssigned=1')

if($searchOnly.IsPresent -or $computer -ne $env:computername) {
	$results.Updates
} else {
	"Downloading updates"
	$updateDownload = $updateSession.CreateUpdateDownloader()
	$updateDownload.Updates =$results.Updates
	$updateDownload.Download()

	"Installing updates"
	$installer = $updateSession.CreateUpdateInstaller()
	$installer.Updates = $updateDownload.Updates
	$installer.install()
}

MS-SQL Powershell database backup

Recently one of our SQL clusters decided it was a good idea to not backup databases anymore. So today my boss told me to get something to bandaid the situation until we can fix the situation (which will likely be an upgrade/reinstall).

To learn more check out the MSDN pages on the Microsoft.SqlServer.Management.Smo namespace.

#
#	Temp database backup
#
$d = date;

#filename: db_full_MM-DD-YYYY-HH.bak
$backupfile = "db_full_" + ($d.ToShortDateString().replace("/","-")) + "-" + ($d.hour) + "h.bak";

#should you use drive paths (ie c:\) it will save to the SQL server's drive.
$backuppath = "\\path\to\save\location\doesnt\need\to\be\unc\";

[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo");
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended");
$ser = New-Object "Microsoft.SqlServer.Management.Smo.Server" "server\instance";
$backup = New-Object Microsoft.SqlServer.Management.Smo.Backup
$bdevice = New-Object microsoft.sqlserver.management.smo.backupdeviceitem -ArgumentList ($backuppath + $backupfile), "File"

$backup.Action = [Microsoft.SqlServer.Management.Smo.BackupActionType]::Database
$backup.BackupSetName = "Daily Full Database";
$backup.Database = "DatabaseName"

$backup.Initialize = $True
$backup.Checksum = $True
$backup.ContinueAfterError = $true
$backup.Devices.Add($bdevice)
$backup.Incremental = $false
$backup.CopyOnly = $true
$backup.LogTruncation ="NoTruncate" #meant for bandaid approach and for less "impact" on database.
$backup.FormatMedia = $false
$backup.SqlBackup($ser); #save it.

Link Dump#1: Je.org

In part of finding out if people like what I’m posting, I from time to time do a google search for the site or myself. Today, I came across some kind of neat links, some of my internet life and some of other people who unfortunatly share my name.

Windows 7 Powershell LogonUI Changer

Based off of the W7C LogonUI Changer, this Powershell script should make changing the LogonUI background easier for the IT admins out there.

Download win7logonui.zip (Includes the ImageProcessor source and DLL)

Usage:

  • After copying or building imageprocessor.dll move it to the same folder as win7logonui.ps1 (or edit the script).
  • Example: win7logonui.ps1 \path\to\image.jpg
  • Set $DebugPreference to “Continue” to watch the script as it runs.

Ad-hoc fileshares

A little concept I’ve been working on every now and again is ad-hoc shares. The reasoning behind it is that committees and other short lived inter-department groups need something to share documents with that can be backed up. Email can be a hassle and quickly fills quotas, IT staff probably hates the idea of users starting shares on their machines (not to mention the backup issue), and a “public” share is way to…public.

A huge part of this idea came from drop.io, which allows anyone to create a place to share files as easily as letting the third parties know the location and password. By utilizing ActiveDirectory and Share and NTFS permissions, you can quickly create a place that people can access files and folders that only they have rights to.

With this script, users can create shares when they need them and limit who has access without having to know anything about share or NTFS permissions. Though at the moment, it would require them to know something about the command line and powershell. However a GUI could be created fairly easily that’s based off of the code below.

One thing that I haven’t finished yet is a culling script which would run on the server the main share is on, which is the reasoning behind the hidden xml file. It holds the info on when to delete the share.

adhocshares.ps1

param (
	$xml = $(throw "You must supply an XML (text not file) configuration!")
)

#
#	settings
#

$version = .2;
$rootdir = "c:\pbin\adhocshares\shares\";

$xmld = new-object system.xml.xmldocument;
$xmld.LoadXml($xml);

#some xml validation here?
$spec_check = $False;

$spec_check = $spec_check -and [bool]$xmld.root.share.dir
$spec_check = $spec_check -and [bool]$xmld.root.share.dir.name
$spec_check = $spec_check -and [bool]$xmld.root.share.dir.expires

$spec_check = $spec_check -and [bool]$xmld.root.share.acl
$spec_check = $spec_check -and [bool]$xmld.root.share.acl.user

if($xmld.root.version.number -ne $version -or $spec_check) {
	throw "XML should conform to the 0.2 version specs.";
}

#
#	Create share
#
$name = $xmld.root.share.dir.name
$newdir = "$($rootdir)$($name)";

##insert code for random name generation.

if(test-path $newdir) {
	throw "Error: Sorry, but that folder already exists. Please try another name.";
}

New-Item $newdir -type directory | out-null

$dacl = get-acl $newdir;
foreach($u in $xmld.root.share.acl.user) {
	"Adding: $($u.name) with $($u.rights) permissions.";
	$inher = ([System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.InheritanceFlags]::ObjectInherit) #[System.Security.AccessControl.InheritanceFlags]::none
	$prop = [System.Security.AccessControl.PropagationFlags]::none

	if($u.rights -eq "rw") {
		$new_rights = New-Object System.Security.AccessControl.FileSystemAccessRule(@("contoso\$($u.name)", "FullControl", $inher, $prop, "allow"));
		$dacl.AddAccessRule($new_rights);
	}
	if($u.rights -eq "ro") {
		$new_rights = New-Object System.Security.AccessControl.FileSystemAccessRule(@("contoso\$($u.name)", "ReadAndExecute", $inher, $prop, "allow"));
		$dacl.AddAccessRule($new_rights);
	}
}
set-acl -path $dacl.path -AclObject $dacl;

#Create the "settings" of the folder.
#Management script will delete folder if .ahs.settings.xml is not present.

$xml_text | out-file "$($newdir)\.ahs.settings.xml";
$t = gi "$($newdir)\.ahs.settings.xml";
$t.set_attributes("Hidden")
$t.set_IsReadOnly($True);

XML permissions

 <?xml version="1.0" encoding='ISO-8859-1'?>
 <!--
	VERSION:0.2
		version:	<version number="adhoc version" />
			Nothing special here other than to specify the parser version.

		share:	<share>
			Similar to "items" for RSS feeds. Meant to one day allow for multiple shares to be created from one xml file.

		dir:	<dir name="share name" expires="date the share will expire and can be deleted" />

		acl:	<acl>
			List of users that will have access to the folder.

		user: <user name="ActiveDirectory SAM account name" rights="(rw|ro)" />
 -->
 <root>
	<version number="0.2" />
	<share>
		<dir name="tmp1" expires="Friday, July 31, 2009 9:32:33 AM" />
		<acl>
			<user name="josherickson" rights="rw" />
			<user name="theboss" rights="ro" />
			<user name="coworker" rights="ro" />
		</acl>
	</share>
</root>

Willmar Developers Guild?

Earlier this week I came across a developers guild for the Twin Cities area and it got me wondering if there was one around here Willmar. The only groups I think would be like this one are the clubs for certain Ridgewater courses or maybe the Willmar Lakes Area Young Professionals. While these groups are great resources, neither seem meant for IS professionals and the field in general.

So…developers and techies in and around Willmar, would you be interested in such a group?