Summary: Microsoft PowerShell MVP Richard Siddaway continues with part five of his awesome Windows PowerShell workflow basics articles.
Microsoft Scripting Guy, Ed Wilson, is here. Today, we have the fifth in a series of guest blog posts written by Windows PowerShell MVP and Honorary Scripting Guy Richard Siddaway dealing with Windows PowerShell workflow.
Note The first post, PowerShell Workflows: The Basics, introduced the basic concepts of Windows PowerShell workflow. The second post, PowerShell Workflows: Restrictions, discussed the restrictions encountered with working with Windows PowerShell workflows. The third article was PowerShell Workflows: Nesting. The fourth article talked about PowerShell Workflows: Job Engine. You should read those articles prior to reading today’s article.
Richard has written a number of guest Hey, Scripting Guy! Blog posts, and he has also written two books on Windows PowerShell. His most written book, PowerShell in Depth, is co-written with fellow MVPs Don Jones and Jeffrey Hicks.
Now, take it away, Richard …
You saw in the last article that Windows PowerShell workflows can survive being stopped and restarted. In certain circumstances, workflows can also survive the Windows PowerShell session being terminated. In this post, you will discover how workflows can survive a reboot.
A computer needs to reboot for a number of activities, for instance:
- Renaming the computer
- Joining or leaving a domain
- Some software installs or updates
- Triggering of a Chkdsk on a system drive
It is possible to perform these activities as a series of steps separated by a reboot. Workflows enable you to set the process off and forget about it until it has finished the processing-reboot-processing cycle.
Consider the following three scenarios:
- Restarting a remote computer against which you are running a workflow.
- Restarting the computer running the workflow and manually restarting the workflow.
- Restarting the computer running the workflow and automatically restarting the workflow.
The first case is the easiest, so that’s where we’ll start.
Restarting a remote computer in a workflow
To keep the examples simple—so you can concentrate on how the workflows deal with the restarts—the following basic workflow forms the basis of the example:
workflow test-restart {
param ([string[]]$computernames)
foreach -parallel ($computer in $computernames) {
Get-WmiObject -Class Win32_ComputerSystem -PSComputerName $computer
Get-WmiObject -Class Win32_OperatingSystem -PSComputerName $computer
}
}
All it does is get the computer system and operating system information via WMI calls. The WMI cmdlets are used rather than the newer CIM cmdlets so that WSMAN versions don’t become a factor. Two remote computers will be used in the test:
- W12standard– Windows Server 2012 (Windows PowerShell 3.0)
- WebR201– Windows Server 2008 R2 (Windows PowerShell 2.0)
The workflow is used like this:
PS> test-restart -computernames w12standard, webr201
In this case, I’m explicitly telling the activities to access a remote computer. This isn’t the only way to achieve this. You’ll learn more about using workflow parameters in the next article.
The remote computers are accessed in parallel with a sequence of two WMI calls applied to each computer. The commands within the foreach block are processed in sequence, but the computers are processed in parallel.
Now you can add the logic to restart the computers.
workflow test-restart {
param ([string[]]$computernames)
foreach -parallel ($computer in $computernames) {
Get-WmiObject -Class Win32_ComputerSystem -PSComputerName $computer
Restart-Computer -Wait -PSComputerName $computer
Get-WmiObject -Class Win32_OperatingSystem -PSComputerName $computer
}
}
The work of restarting the remote computers is done by this line:
Restart-Computer -Wait -PSComputerName $computer
The important parameter is –Wait. It tells the workflow to wait until the remote computer has restarted before progressing to the next command.
If you run this in Windows PowerShell Integrated Scripting Environment (ISE) or the Windows PowerShell console, you will see a progress bar with messages informing you of the stages of the restart. These include:
- Waiting for restart to begin
- Verifying computer has restarted
- Waiting for WMI connectivity
- Waiting for Windows PowerShell connectivity
- Waiting for WinRM connectivity
In my example, Windows Server 2012 restarts a lot faster than Windows Server 2008 R2.
In the workflow topic Restarting the Computer in a Workflow, the following code can be found:
Restart-Computer -Wait -PSConnectionRetryCount 4 -PSConnectionRetryInterval 5
Note The PSConnectionRetryCount and PSConnectionRetryInterval parameters do not appear to be recognized as workflow activity parameters.
You can always run the workflow as a Windows PowerShell job:
PS> test-restart -computernames w12standard, webr201 -AsJob
Id Name PSJobTypeName State HasMoreData Location
-- ---- ------------- ----- ----------- --------
25 Job25 PSWorkflowJob Running True localhost
After the job has finished, you can examine the data. All you get is the returned WMI data. The progress messages during the reboots will be supressed.
Restarting the computer running the workflow
Two options exist here:
- Restart the workflow manually
- Restart the workflow automatically
Let’s start with a manual restart.
Manual restart of workflow
Running a workflow that reboots the computer it’s running on and then manually restarting the workflow is not an optimal solution—ideally, you want the computer to do all the work. But it involves less coding and is a simpler solution. There are times when quick and simple gets the job done. This approach also forms the basis for starting the workflow automatically.
In this example, I’m going to show how to use the new Rename-Computer cmdlet (workflow activity). Renaming a computer running Windows is one of the examples that requires a reboot.
workflow rename-localsystem {
param (
[string]$newname
)
Rename-Computer -Newname $newname -Force -Passthru
Restart-Computer -Wait
Get-CimInstance -ClassName Win32_ComputerSystem |
Select-Object -ExpandProperty Name |
Set-Content -Path "C:\Scripts\$newname.txt"
}
The workflow takes the new computer name as a parameter. It uses that name to perform the rename, and then uses Restart-Computer with the –Wait parameter. After the reboot, the name of the computer is retrieved and put into a text file of that name.
You can start by manually displaying the current computer name. In this case, it is TEST1NOJOBPARAM. It’s a name the system ended up with from some previous tests.
PS C:\scripts> $env:COMPUTERNAME
TEST1NOJOBPARAM
PS C:\scripts> rename-localsystem -newname W12SUS
You can then run the workflow supplying the new name. Notice that the –AsJob parameter hasn’t been used. The rename occurs and the system reboots.
After the reboot, you can connect to the computer and test for the file.
PS C:\scripts> ls
Directory: C:\scripts
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 14/12/2012 18:45 Setup
-a--- 02/01/2013 11:58 280 rename-localsystem.ps1
The file hasn’t been produced. A suspended job exists, as shown below.
PS C:\scripts> Import-Module PSWorkflow
PS C:\scripts> Get-Job | Format-Table -AutoSize
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
3 Job2 PSWorkflowJob Suspended True localhost rename-localsystem
Resume the job. The job finishes and the file is produced. As a final test, you can check the environmental variable for the computer name.
PS C:\scripts> ls
Directory: C:\scripts
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 14/12/2012 18:45 Setup
-a--- 02/01/2013 11:58 280 rename-localsystem.ps1
-a--- 02/01/2013 12:24 8 W12SUS.txt
PS C:\scripts> $env:COMPUTERNAME
W12SUS
There will be times when you need to restart the computer, but the workflow terminates at that point. This is a perfectly legitimate scenario, as shown in this example:
workflow add-localsystemtodomain{
param (
$domcred,
[string]$ipv4address
)
$index = Get-NetIPInterface -AddressFamily IPv4 -Dhcp Enabled |
Select-Object -ExpandProperty ifIndex
New-NetIPAddress -InterfaceIndex $index -AddressFamily IPv4 `
-IPAddress $ipv4address -PrefixLength 24
Set-DnsClientServerAddress -InterfaceIndex $index -ServerAddresses "10.10.54.201"
Set-DnsClient -InterfaceIndex $index -ConnectionSpecificSuffix "manticore.org"
Add-Computer -Credential $domcred -DomainName Manticore `
-OUPath "OU=Security Servers,OU=Servers,DC=Manticore,DC=org" -Force
Restart-Computer
}
## remove relicit jobs
Get-Job |
where PSJobTypeName -eq "PSWorkflowJob" |
Remove-Job
$cred = Get-Credential
add-localsystemtodomain -domcred $cred -ipv4address "10.10.54.170" -JobName AddToDomain
The new networking cmdlets are used to set the static IP address of the virtual machine’s network adapter. The DNS servers and connection suffix are also set. The workflow adds the computer to the domain and then reboots.
The script cleans up any workflow-related jobs, asks for the credentials used to join the domain, and then fires the workflow. The –Wait parameter isn’t used on Restart-Computer, so a Windows PowerShell job isn’t produced and no further processing occurs.
What about a situation where you want to control the restarting of the workflow, but then let the system take care of it?
Automatic restart of workflow
The online documentation I have seen states that a Windows PowerShell scheduled job can be used to perform this task. I had zero success with that approach, so I switched to using a scheduled task. Windows PowerShell 3.0 introduced a set of cmdlets for working with scheduled tasks. These are WMI-based and are only available on Windows 8 and Windows Server 2012. It would be possible to duplicate this functionality by using the COM interface to the task scheduler, but that is a task for another day.
I went for a very simple approach while testing this because of the problems I was having, but there is enough here to show you the concept. Let’s start with a workflow:
workflow test-restart {
Get-WmiObject -Class Win32_ComputerSystem | Out-File -FilePath C:\Reports\comp.txt
Get-ChildItem -Path C:\Reports | Out-File -FilePath C:\dir.txt
Restart-Computer -Wait
Get-WmiObject -Class Win32_OperatingSystem | Out-File -FilePath C:\Reports\os.txt
}
The workflow uses Get-WmiObject to get the Win32_ComputerSystem class and creates a file containing the data. Get-ChildItem produces a listing of the folder containing the report. This tests that the first step in the workflow was performed.
Restart-Computer is used with the –Wait parameter to produce a suspended workflow job and the computer reboots.
After the system restarts, the workflow is resumed by a scheduled task, and the last step of producing a file containing data from the Win32_OperatingSystem class is performed.
Next step is to create the scheduled task:
$actionscript = '-NonInteractive -WindowStyle Normal -NoLogo -NoProfile -NoExit -Command "&''c:\reports\test-resume.ps1''"'
$pstart = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
Get-ScheduledTask -TaskName Test | Unregister-ScheduledTask -Confirm:$false
$act = New-ScheduledTaskAction -Execute $pstart -Argument $actionscript
$trig = New-ScheduledTaskTrigger -AtLogOn
Register-ScheduledTask -TaskName Test -Action $act -Trigger $trig -RunLevel Highest
I want my scheduled task to run Windows PowerShell, so I define the starting parameters for Windows PowerShell in the $actionscript variable. $pstart contains the command to start Windows PowerShell.
The next line removes any instances of the scheduled task.
The task action is defined by using New-ScheduledTaskAction. A trigger is defined to control when the task fires. I’ve set this to be when I log on, because I wanted to see the task run. Change –AtLogon to –AtStartUp, if required.
Register the task data to create the task. Ensure that –RunLevel Highest is included, so your task runs with elevated privileges.
The last bit of the puzzle is the script that the scheduled task calls:
Get-ChildItem -Path C:\Reports | Out-File -FilePath C:\Reports\dir.txt
Import-Module PSWorkflow
Get-Job | Resume-Job
This creates another directory listing, imports the workflow module, and then resumes the suspended job. You can filter on the job name if you might have more than one suspended job on the system you are restarting.
After the computer has restated, and you’ve logged on, you’ll see a Windows PowerShell window open and the workflow job will continue.
One oddity is that the timestamp on the os.txt file matches that of the comp.txt file! The os.txt file is definitely produced after the reboot though.
Clean up the job in the usual way and remove the scheduled task
Get-ScheduledTask -TaskName Test | Unregister-ScheduledTask -Confirm:$false
This doesn’t seem to be an elegant solution, but it works. I’ll keep looking at the scheduled job option and blog about it if I get it working.
Gotchas
One very big gotcha that I’ve discovered testing this functionality is that you shouldn’t run workflows through ISE.
According to the documentation, you can run a workflow like this:
workflow test-restart {
Get-WmiObject -Class Win32_ComputerSystem
Restart-Computer -Wait
Get-WmiObject -Class Win32_OperatingSystem
}
If you run it through ISE, you won’t get a suspended job—it’ll carry on to completion. Run it through the console and everything works as expected. Many thanks to Steven Murawski for helping me track that one down.
The other thing to remember is to save any output to disk or you will lose it on reboot.
Conclusion
After working through these scenarios, I’ve come to the conclusion that running the workflows against a remote computer when you need a restart is by far the easiest way to do it. Rebooting the local computer during a workflow is possible, but it can involve a lot of head-scratching to get it working the way you want.
Thank you, Richard—another great job.
Join me tomorrow for more cool Windows PowerShell stuff.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy