Or at least not entirely useless

Server Group Patching: Node Scripts

I recently tried to implement Server Group Patching to patch a group of servers in a particular order and failed because that apparently does not work.  Before I got there though I needed to set up some node scripts.  In this instance we needed to tell Exchange to use another node in the database availability group (DAG) so that we could patch and reboot the current server.  That seems like the exact kind of thing the node scripts were designed for.  However, we came up against a couple of issues that I discuss below.

Node Script Permissions

The node scripts will run under the local system account.  My Exchange admins were uncomfortable granting the local systems access to administrate Exchange so they created a task in Task Scheduler to run under a service account.  From there I could use the node drain scripts to initiate those tasks.  When creating a node script remember to take in account that currently your only option is to run the script under the device’s local system account.

Node Script Length

When creating a node script you are limited to 512 characters.  That’s right, you are supposed to create a node drain script in less space than you get to tweet four times.   It took a few iterations but eventually I came up with the script below.  It will run the named Scheduled Task, wait until it finishes, and then exit with the result code.

$ST=Get-ScheduledTask $STN 
If($ST -eq $null){[Environment]::Exit(0)} 
If($STInfo -eq $null){[Environment]::Exit(0)} 
 Start-Sleep -seconds 5 
 $STInfo = $ST | Get-ScheduledTaskInfo 
 If($STInfo -eq $null){[Environment]::Exit(1761)} 
 If($STInfo.LastRunTime -gt $LRT) {[Environment]::Exit($STInfo.LastTaskResult)} 

Where’s the Logs?

If you want to check on the script result then look at the client’s UpdatesDeploymentAgent.log file. It should show something like this:

Powershell path: C:\Windows\system32\WindowsPowerShell\v1.0\PowerShell.exe
Running: "C:\Windows\system32\WindowsPowerShell\v1.0\PowerShell.exe" -NoProfile -ExecutionPolicy RemoteSigned -File C:\Windows\TEMP\CCME7B.tmp.ps1
Executing command line: Run Powershell script
Process completed with exit code 0

What could go wrong?

What if the node script were to fail for some reason? The log would look something like this:

Available to run updates.
Running: "C:\Windows\system32\WindowsPowerShell\v1.0\PowerShell.exe" -NoProfile -ExecutionPolicy RemoteSigned -File C:\Windows\TEMP\CCM1B08.tmp.ps1
Process completed with exit code 123
Non-zero exit code. 123.
RunPowershellScript() failed. 0x80004005.

In the console the updates will report a deployment status of “Failed to install update(s)” which is a reasonable choice. However, the Last Enforcement Error Code will be 0x87D00667 which translates to ‘No current or future service window exists to install software updates’.  So be sure to watch out for that one. The support engineer created his own Desired Change Request for better errors which you can find here: Exit code for Node drain/resume scripts (when ‘Server Group’ feature is enabled) is not uploaded in State Message 613.

Is There a Better Way?

While the above issues can be worked around it feels out of place given that the feature is named and designed to drain and fill cluster nodes.  It seems impossible to drain a cluster node in a safe way with 512 characters.  With the new ‘Run Powershell Script’ feature introduced in Current Branch 1706 I think the way forward is clear … integrate the two features.  Add in a run-as component and the peasants will rejoice.  If you feel the same, be sure to say as much on UserVoice: Implement native cluster aware patching within SCCM to support cluster aware updating.



  1. Adam Gloyd

    Thanks for the article. I’ve been using server groups for awhile just to use the benefit of deployment locks for simpler clusters and web servers behind a NLB. Finally decided to automate a more complex cluster (our phone system) only to find that our drain script doesn’t fit. I requested a couple of related features on UserVoice and would appreciate some votes:

  2. Mr. Drain

    Thanks for the script.
    In line 4 a $ is missing. Should be $STInfo=$ST|Get-ScheduledTaskInfo

    • bryandam

      Sorry for the late reply but I’ve updated the script.

  3. Chris

    Bryan, in an effort to get around the 512 char limit… Do you know if you can save a longer powershell function as a .psm1 file, and just call it with an import-module c:\somefolder\somefile.psm1. This way you could pass bits of information to the module.

Leave a Reply

© 2022 Dam Good Admin

Theme by Anders NorenUp ↑