Add Automatic Confirmation and WhatIf to Your Scripts

Recently, a student implemented a function to stop a service remotely via WMI. Once done, she was concerned that someone accidentally stopped a service and wanted to add some sort of confirmation.

Fortunately, she did not really have to implement confirmation herself. PowerShell can do that for you. Here is how.

First: Start (and stop) services remotely

Before we look at how automatic confirmation works, let's first look at the original challenge: how would you start or stop a service remotely? Start-Service and Stop-Service cmdlets unfortunately do not implement a -ComputerName parameter and only work locally.

WMI works locally and remotely. To access a remote service, use Get-WMIObject and the Win32_Service class. Here is how you can access the Spooler service on a remote system:

$service = Get-WMIObject Win32_Service -computername server1 -filter 'name="Spooler"'

If you need to authenticate as a different user, feel free to add the -Credential parameter.

The $service object has a number of method that you can use to start or stop a service:

$service | Get-Member -member Method

To stop the spooler service on the remote system, this is how you'd do it:

$service.StopService().ReturnValue

So a PowerShell function to restart a service remotely would look like this (don't type this code in, just hold on a second, we are going to add some few final lines to this code in a second):

function Restart-ServiceEx {
param(
$computername = 'localhost',
$service = 'Spooler',
$credential = $null
)

# if credential was specified, use it...
if ($credential) {
$service = Get-WmiObject Win32_Service -ComputerName $computername -Filter "name=""$service""" -Credential $credential
} else {
# else do not use this parameter:
$service = Get-WmiObject Win32_Service -ComputerName $computername -Filter "name=""$service"""
}

# if service was running already...
if ($service.started) {
# stop it...
$rv = $service.StopService().ReturnValue
if ($rv -eq 0) {
# ...and if that worked, restart again
$rv = $service.StartService().ReturnValue
}
} else {
# else if service was not running yet, start it:
$rv = $service.StartService().ReturnValue
}

# let people know if things went alright
$errorcode = 'Success,Not Supported,Access Denied,Dependent Services Running,Invalid Service Control'
$errorcode += ',Service Cannot Accept Control, Service Not Active, Service Request Timeout'
$errorcode += ',Unknown Failure, Path Not Found, Service Already Running, Service Database Locked'
$errorcode += ',Service Dependency Deleted, Service Dependency Failure, Service Disabled'
$errorcode += ',Service Logon Failure, Service Marked for Deletion, Service No Thread'
$errorcode += ',Status Circular Dependency, Status Duplicate Name, Status Invalid Name'
$errorcode += ',Status Invalid Parameter, Status Invalid Service Account, Status Service Exists'
$errorcode += ',Service Already Paused'
$errorcode.Split(',')[$rv]
}

Adding Support for WhatIf and Confirm

To add support for -Confirm and -Whatif, have a look at this simply prototype function:

function Test-Function {
[CmdletBinding( SupportsShouldProcess=$true)]
param()

if ($pscmdlet.ShouldProcess('Source', 'Action to take')) {
'I am actually doing something here!'
}
}

When you run Test-Function, it will output a text. If you call it with the -whatif parameter, it automatically skips the action and instead prints out what it would have done. You can control this text yourself. Have a look at the code: ShouldProcess takes two arguments, the source where the action is going to take place, and a description of the action. Finally, -Confirm also works. It automatically runs a confirmation dialog, and the user can decide to take the action or skip it.

With this in mind, we can now add the -Whatif and -Confirm support to Restart-ServiceEx. You can download the function here: http://powershell.com/cs/media/p/8389.aspx

function Restart-ServiceEx {
[CmdletBinding( SupportsShouldProcess=$true, ConfirmImpact='High')]
param(
$computername = 'localhost',
$service = 'Spooler',
$credential = $null
)

# create list of clear text error messages
$errorcode = 'Success,Not Supported,Access Denied,Dependent Services Running,Invalid Service Control'
$errorcode += ',Service Cannot Accept Control, Service Not Active, Service Request Timeout'
$errorcode += ',Unknown Failure, Path Not Found, Service Already Running, Service Database Locked'
$errorcode += ',Service Dependency Deleted, Service Dependency Failure, Service Disabled'
$errorcode += ',Service Logon Failure, Service Marked for Deletion, Service No Thread'
$errorcode += ',Status Circular Dependency, Status Duplicate Name, Status Invalid Name'
$errorcode += ',Status Invalid Parameter, Status Invalid Service Account, Status Service Exists'
$errorcode += ',Service Already Paused'

# if credential was specified, use it...
if ($credential) {
$service = Get-WmiObject Win32_Service -ComputerName $computername -Filter "name=""$service""" -Credential $credential
} else {
# else do not use this parameter:
$service = Get-WmiObject Win32_Service -ComputerName $computername -Filter "name=""$service"""
}

# if service was running already...
$servicename = $service.Caption
if ($service.started) {
# should action be executed?
if ($pscmdlet.ShouldProcess($computername, "Restarting Service '$servicename'")) {
# yes, stop service:
$rv = $service.StopService().ReturnValue
if ($rv -eq 0) {
# ...and if that worked, restart again
$rv = $service.StartService().ReturnValue
}
# return clear text error message:
$errorcode.Split(',')[$rv]
}
} else {
# else if service was not running yet, start it:
if ($pscmdlet.ShouldProcess($computername, "Starting Service '$servicename'")) {
$rv = $service.StartService().ReturnValue
$errorcode.Split(',')[$rv]
}
}
}

Adding ConfirmImpact

As you can see, it is usually up to the user whether or not he uses -Confirm. If the action your function takes is severe enough, you may want -Confirm to be the default so that you get a confirmation dialog automatically. This is why you as the author of the function can declare a ConfirmImpact level. By default, PowerShell runs the confirmation logic automatically for any function that has a ConfirmImpact level of High. Exchange this line

[CmdletBinding( SupportsShouldProcess=$true)]

to this line:

[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]

When you now run the function, it will always ask for confirmation, and you do not have to explicitly use -Confirm.

Some Extra Tricks

The automatic variable $ConfirmPreference determines the ConfirmImpact level that will trigger automatic confirmation. Valid values are None, Low, Medium and High. If you set this variable to "Low", you will get confirmations for all cmdlets and all functions supporting confirmation. If you set it to "None", you will never get automatic confirmations.

If you must run commands unattended and want to make sure no implicit confirmation gets in your way and blocks the script, you can either set $ConfirmPreference to "None", or you can explicitly assign a $false to your -Confirm parameter. You can test this with Restart-ServiceEx:

Restart-ServiceEx -Confirm:$false

Have fun! See you next time around,

Tobias

Microsoft MVP PowerShell Germany

P.S.
If you live in Germany or other parts of Europe and your company would like to set up a truly great PowerShell training, just contact me! I regularly train mid- to large-size companies. Trainings are always a blast with tons of real-world-examples and solutions. Here's how to get in touch with me: tobias.weltner@scriptinternals.de

 

 

 


 


Posted Nov 18 2010, 11:19 PM by Tobias
Concentrated Tech NSoftware Dell Compellent Sponsored by Idera and Concentrated Tech and NSoftware and Dell Compellent
Copyright 2011 PowerShell.com. All rights reserved.