Use a PowerShell Script to Start Non-Running Automatic Services

Use a PowerShell Script to Start Non-Running Automatic Services

  • Comments 13
  • Likes

Summary: Guest blogger, Karl Mitschke, discusses a Windows PowerShell script to start non-running services that are set to start automatically.

Microsoft Scripting Guy, Ed Wilson, is here. Our guest blogger today is Karl Mitschke, one of the authors of the Windows PowerShell 2.0 Bible. Here is a little bit about Karl.

Karl Mitschke is an IT veteran with over 20 years of experience. He has specialized in messaging since the early 1990s. Karl has been automating tasks with script since moving to Microsoft Exchange 5.0, starting with WinBatch. He started using Windows PowerShell in 2007 when he moved to Exchange Server 2007. When he’s not writing Windows PowerShell scripts, or writing about  Windows PowerShell scripts, Karl enjoys spending time with his bride, Sherry, and their two dogs.
Blog: Unlock-PowerShell
Windows PowerShell 2.0 Bible

Starting services on remote servers

In the “Performing Advanced Server Management” chapter in the Windows PowerShell 2.0 Bible, I presented a script that would start services on remote servers that are set to start automatically and were not started. Due to the constraints of the book, the script was severely limited. That script is shown here.

$Computers = “FileServer01”,”FileServer02”
$WmiObject = @{
Class = “Win32_Service”
Filter = “StartMode=’Auto’ and State!=’Running’”
}
foreach ($Computer in $Computers)
{
foreach ($Svc in Get-WmiObject @WmiObject -ComputerName $Computer)
{
Write-Host “Starting the” $Svc.DisplayName “service on $Computer”
$Svc.StartService() | Out-Null
}
}

This was fine, but the script could really use some improvement. For one thing, you needed to hard code the server names. In addition, it did not accept credentials, you could not simulate the action, and there was no Help.

The concept was sound, however, so I decided to extend the script to put it into production at work, where I use it to verify that services are running after performing updates on our Exchange Server infrastructure. Unfortunately, extending the script makes it too long for the book and too long for this blog. The complete script is available in the Scripting Guys Script Repository, but I will discuss highlights of the script in this blog post.

Extending the script with Help

The first several objections are easy to overcome with advanced parameters. The final objection is overcome with comment-based Help. Windows PowerShell even includes a method to add Help. I am referring to the built in Get-Help cmdlet. In fact, you can potentially get more Help than you will ever need by typing the following script into your Windows PowerShell console:

Get-Help -Name functions_advanced_parameters

Get-Help -Name about_comment_based_help

The Help about advanced parameters is too verbose to summarize, so I would suggest that you read it and familiarize yourself with the content.

The Help for comment-based Help shows that for script-based Help, comment-based Help can appear in two locations:

  • At the beginning of the script file. Script Help can be preceded in the script only by comments and blank lines. If the first item in the script body (after the Help) is a function declaration, there must be at least two blank lines between the end of the script Help and the function declaration. Otherwise, the Help is interpreted as being Help for the function, not Help for the script.
  • At the end of the script file. If the script is signed, place comment-based Help at the beginning of the script file. The end of the script file is occupied by the signature block.

As I sign the scripts that I use at work, I include comment-based Help at the beginning of the file.

I also choose to use the comment-block syntax as opposed to using the comment character (#) in front of each line, because I find it to be more readable. An example of the comment-block syntax is shown here.

<#

.SYNOPSIS

Invoke-StartService - Start all essential services that are not running.

.DESCRIPTION

This script finds services that are set to start automatically, and starts them.

#>

Therefore, Help is out of the way with the comment-based Help. Next, I tackle the parameters.

Adding arguments

I wanted to allow the script to accept server names from the command line or from a pipeline, so I used the ValueFromPipeline argument when I declare the ComputerName parameter for the script. I also want to allow the script to target multiple computers, so I declared the ComputerName parameter as an array of strings as shown here.

[Parameter(

Position = 0,

ValueFromPipeline=$true,

Mandatory = $false,

HelpMessage = "The computer to start services on"

)]

[string[]]$ComputerName = $env:ComputerName

As you can see, I set the ComputerName parameter to not be required by using the local computer name if it is not specified. I also set the parameter to be positional—that means that you do not need to use the name of the parameter as long as the computer name(s) are specified as the first-passed parameter to the script.

I wanted the script to accept credentials so that I could start my Windows PowerShell session with a non-administrator account. I added the optional parameter Credential to handle these credentials.

Finally, I wanted to emulate the WhatIf functionality in so many of my favorite cmdlets, so I added the optional switch parameter WhatIf. The entire parameter declaration is shown here.

param (

[Parameter(

Position = 0,

ValueFromPipeline=$true,

Mandatory = $false

)]

[string[]]$ComputerName = $env:ComputerName,

[Parameter(

Position = 1,

Mandatory = $false

)]

$Credential,

[Parameter(

Mandatory = $false

)]

[switch]$WhatIf

)

Using $PSBoundParameters

I wanted to avoid If statements as much as possible, so I take advantage of the automatic variable $PSBoundParameters, which is a hash table of the parameter names and values that are passed to the script.

You can pass the $PSBoundParameters to the Get-WmiObject cmdlet as a splatted hash table as shown here:

Get-WmiObject -Class Win32_Service @PSBoundParameters

The parameters ComputerName and Credential can be passed directly to the Get-WmiObject cmdlet. However, that cmdlet does not accept the WhatIf parameter, if present, which would cause the error message shown here.

Image of error message

This can be resolved by using the Remove() method of the hash table. You can check for the existence of the parameters with an If statement, or you can pass the command to the Out-Null cmdlet to avoid the If statement as shown here.

$PSBoundParameters.Remove("WhatIf") | Out-Null

I also have to handle the two methods of passing credentials to the script. The parameter Credential is not typed, so it accepts a string or a credential object as shown here.

$cred = Get-Credential -Credential "mitschke\karlm"

.\Invoke-StartService.ps1 -Credential $cred

When it is supplied like that, the $PSBoundParameters hash table passes a credential object to the Get-WmiObject cmdlet. However, if you supply the Credential parameter with a string, allowing the script to prompt for a password, the $PSBoundParameters hash table passes the string to the Get-WmiObject cmdlet and prompts for a password on every service that needs starting.

This is resolved by once again using the Remove() method of the hash table, and then using the Add() method to add the valid credential object to the $PSBoundParameters hash table as shown here.

if ($Credential -ne $null -and $Credential.GetType().Name -eq "String")

{

$PSBoundParameters.Remove("Credential") | Out-Null

$PSBoundParameters.Add("Credential", (Get-Credential -Credential $Credential))

}

I modified the Filter parameter for the Get-WmiObject cmdlet to find only the services that I was interested in. Specifically, I am not interested in starting the Microsoft .NET Framework Runtime Optimization Services, which may be set to start automatically. The service would start and then stop because it finds that no action is needed. It is not really a problem starting these services, I just wanted to avoid the time it takes to start them, and avoid event log entries that show they had started and stopped.

These services have a service name starting with clr_optimization_. The filter I use is shown here.

Filter "startmode='auto' and state='stopped' and (name > 'clra' or name < 'clr')"

The filter isn’t perfect—I suppose there could be a service with a name that starts with clra, but I have not seen one, so the filter serves my purposes. A better filter could use the grave accent character (`). The grave accent character is also the escape character for Windows PowerShell, so you would need to use two grave accents as “name > 'clr``’”. You could also use a client-side filter by using the Where-Object cmdlet as shown here:

Get-WmiObject -Class Win32_Service | Where-Object -FilterScript {$_.Name -notmatch "clr_*" -and $_.StartMode -eq "Auto" -And $_.State -ne "Running"}

That is perfectly valid; however, using the Filter parameter of the Get-WmiObject cmdlet performs server-side filtering, which should be quicker because only the objects we are interested in are passed back to the client. The complete server-side filtering example is shown here:

Get-WmiObject -Class Win32_Service –Filter "startmode='auto' and state='stopped' and (name > 'clra' or name < 'clr')"

So now we have a working filter, working parameters, and working Help. It is now time to test the parameters with the WhatIf switch. The command line and its associated output are shown here.

Image of command output

When I see what the script will do, I run the script without the WhatIf parameter. The output from the command appears in the following image.

Image of command output

As mentioned, the entire script is available in the Scripting Guys Script Repository. The output of the script follows the script. I believe the script is pretty well written; but as always, I am open to suggestions. You can reach me at:

-join ("6B61726C6D69747363686B65406D742E6E6574"-split"(?<=\G.{2})",19|%{[char][int]"0x$_"})

Thank you Karl, for a very useful and interesting blog post. Join me tomorrow for more Windows PowerShell goodness.

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 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Nice blog, Karl.  I especially like the idea of server-side filters, but I'm not mad about the way you filtered out clr_*.  In this case, I would combine the two methods of filtering.  This would still be efficient, because you would still be eliminating most of the records with your server-side filter:

    Get-WmiObject -Class Win32_Service –Filter "startmode='auto' and state='stopped'" | `

       Where-Object -FilterScript { $_.name -notlike 'clr_*' }

  • Hi Karl,

    an excellent blog entry that shows some aspects of server management with powershell!

    Though I can imagine how the script looks .. it is not available through the link!

    Maybe it's updated automatically later on ... so we might need to wait til tomorrow :-)

    I agree with bigteddy that efficiency may not be the problem in an environment with some servers. Of course this problem may scale if more than "some servers" are involved!

    But I think the filter expression could well be narrowed a bit further if you use "not ... like" in it:

    Get-WmiObject -Class Win32_Service –Filter "startmode='auto' and state='stopped' and not (name like 'clr_optimization_%')"

    This might be close enough to exclude the matching services and only them!

    Using $PSBoundParameters is a common technique that works well and it's really great that we can modify the collection in the function itself to get rid of parameters that we don't want to evaluate more than once or which are not understood by other Cmdlets.

    Klaus.

  • Thanks, Grant;

    I wasn't really fond of the filter either.

    I initially was using you version, and just don't like using Where-Object if I can avoid it..

    I like the filter Klaus provided better :)

  • Thanks, Klaus;

    I have no problem finding the script when I click the link.

    gallery.technet.microsoft.com/Starts-automatic-on-remote-26ddd199

    Thanks for the awesome filter too!

  • I noticed that I claim I define the parameter "WhatIf" - that's incorrect - I allow the SupportsShouldProcess=True to provide this functionality - OOPS :)

  • Hi Karl,

    sorry 4 that!

    You have been absolutely right!

    I could have downloaded the script, too!

    I just have been missing the text of the script that is usually displayed on the site ...

    I see that we have to accept the license before we can access the script.

    So the script is not shown on the linked site, but we can dowbload it!

    My fault!

    And: Yes, "SupportsShouldProcess=True" is required to support the "-whatif" and "-confiirm" switches ...

    Klaus.

  • Hi Karl,

    @everybody: You have to download the script first!

    There is one error in the downloaded script that leads to a strange behaviour if the command to start a process eg. fails. In this case the default of the switch statement close to the bottom of the script show Write-Error ".... some text ... " but it doesn't do anything! Only the surrounding try-catch-block displays an error message!

    The reason: the line:

    default { Write-Error "... $($ReturnCode[$Result.ReturnvValue])." }

    tries to index into $ReturnCode with Index: "$Rresult.ReturnValue" which is impossible of course because $Result is an [int32] and a variable $ReturnValue doesn't exist!

    One other little thing that just looked curious on first sight ... maybe just to me:

    I personally would prefer to surround ALL string conditions in the "switch ( ....State)" statement with double quotes! I know it is unneccessary sometimes, but imho. it looks and feels much better if it is uniformly done ... especially if syntaxhighlighting is turned on which in fact was the reason that made me wonder :-)

    In the ISE the one word conditions without double quotes are violet and the composed words with double quotes are brown.  That's a little bit confusing on first sight!

    One last thing I personally prefer if I have to populate larger hash tables, as you did in the beginning with $ReturnCode ( which is a wonderful idea btw. !!! )

    Using here strings and ConvertFrom-StringData to build the hash table saves a lot of typing ( especially all the double quotes )

    Klaus.

  • Thanks for the feedback, Klaus;

    I had an error on the default line. (Line 120)

    I have updated the script on the repository, but there is no need to download again, the changed line is:

    default {Write-Error -Message "Error $($Verb) service '$($Service.DisplayName)' on $($Service.__SERVER). Error: $($ReturnCode[$Result])."}

    Karl

  • I have updated the script once again;

    It now properly handles an array of computers.

    Karl

  • quite useful. on my server when a service doesn't start via this script like a time out or dependency the script stops. i'm new to PS scripting and wondering what is a way to tell the script to try again a certain amount of times if error on start then go to next invocation.

    maybe i could store those that don't start in an array and print to screen and prompt to try again or quit.

    but for now, would be nice to know how to get it to not stop/exit on error

    thanks for the great script.

  • would someone recommend a good PS book.

    thnx

  • I am in desperate need of getting this script to work. I have like 1000 servers I have to manage and I would be the hero of the century if I could get this to work. It seems to run fine with the "what if" parameter but when I remove that piece I get the error that's coded into the bottom of the script that it cannot connect to the services for server1, server2, server3, etc. on my servers.txt list. Is this something on the client end? I would think if there was an issue with powershell connectivity of some sort that it would bomb out on the -what if part, but maybe without the -what if it needs to have higher rights or something? I'm running this as the domain admin. Any help to get this working would be greatly appreciated!!!! $50 gift card to Outback or something - ha. This would saves me hours of hell.

  • Karl - the line you fixed on 13-Dec-2011:

    default {Write-Error -Message "Error $($Verb) service '$($Service.DisplayName)' on $($Service.__SERVER). Error: $($ReturnCode [$Result])."}


    is throwing an error when I attempt to run it on Server 2012:

    At C:\scripts\Invoke-StartService.ps1:129 char:2
    + [$Result])."}
    + ~
    Missing type name after '['.
    + CategoryInfo : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : MissingTypename


    Thoughts?