Using a Function to Set unique variables with a Service Status, per server

Hi, I’m trying to write a script that’s confusing me, it will:

  1. Invoke different servers, to get the current status of a specific windows service and put that status in a unique global variable (That can be accessed outside of the function which invoked servers, to know if the service on that server is running or not)
  2. Stop the service (if needed)
  3. Run some of my upgrades
  4. Start the service later (If it was running before I started)

The values I have set below that are above my script are just for this example to see it visually, as I am actually reading the 3 values from an .xml file elsewhere.

Variables Already Set:
$ServiceName=“MyWindowsService”
$Server1=“SVR01”
$Server2=“SVR02”

With the values above in mind I have tried the script:

## The Function
Function Get-ServiceStatus {
    param (
        $ServiceName,
        $Server1,
        $Server2
    )

    ## Invoke Servers to get a Service Status and set a variable that is based on the servers name
    Invoke-Command -ComputerName $Server1, $Server2 -ScriptBlock {
        $ServiceStatus = Get-Service $Using:ServiceName
        ${env:COMPUTERNAME State}=$ServiceStatus.Status
    }
}

## Call the Function
Get-ServiceStatus -ServiceName $ServiceName -Server1 $Server1 -Server2 $Server2

## See what is returned
Write-Host "Service Current State 1= ${$Server1 State}"  <#Should say "Stopped" #>
Write-Host "Service Current State 2= ${$Server2 State}"  <#Should say "Running" #>

Any help on this would be much appreciated.

Is the ENV variable is not being set?

I found this in the helpfile for about_environment_provider:

You can also change the value of an environment variable using the $env:
variable prefix. Any changes made only pertain to the current PowerShell
session for as long as it is active.

Adam1987
Welcome to the forums.

I’d recommend not to manipulate variable outside the scope of the function. Better would be to output the status of the service as the return value of the function.

In the best case your function takes a single server name or an array of server names and a single service or an array of services and outputs the results as its return values. You could assign this output to a variable and use this variable for further steps.

A simple version would look like this:

Function Get-ServiceStatus {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipeline = $true)]
        [String[]]
        $Service,
        [Parameter(Mandatory = $true,
            ValueFromPipeline = $true)]
        [String[]]
        $ComputerName
    )
    $StateList = 
    Invoke-Command -ComputerName $ComputerName -ScriptBlock {
        Get-Service $Using:Service         
    }
    foreach ($State in $StateList) {
        [PSCustomObject]@{
            Name         = $State.Name
            DisplayName  = $State.DisplayName
            Status       = $State.Status
            ComputerName = $State.PSComputerName
        }
    }
}

And you could use it like this:

Get-ServiceStatus -Service 'W32Time', 'Spooler' -ComputerName 'PC01', 'PC02'

And the output would look like this:

Name    DisplayName        Status  ComputerName
----    -----------        ------  ------------
Spooler Druckwarteschlange Running PC02
W32Time Windows-Zeitgeber  Running PC02
Spooler Druckwarteschlange Running PC01
W32Time Windows-Zeitgeber  Running PC01

My ouptut of “DisplayName” is in German because my Windows is in German. :wink:

2 Likes

Thanks for making me aware of the PSCustomObject, however, how can I use that later on in my script to then start them again (if they were started in the first place, after I have run other tasks during my script go back and start them again automatically)? After I have got these values in a unique variable, I will want to use them later on in the script after doing some other work first. I will want to revisit these values with the concept of “If the Service was running at the start of the script on a particular sever, invoke that server and start it again”

Values:
$ServiceName=“MyWindowsService”
$Server1=“SVR01”
$Server2=“SVR02”

Function Get-ServiceStatus {
    param (
        $ServiceName,
        $Server1,
        $Server2
    )

    $StateList =
    Invoke-Command -ComputerName $Server1, $Server2 -ScriptBlock {
        Get-Service $Using:ServiceName
    }

    foreach ($State in $StateList) {
        [PSCustomObject]@{
            Name         = $State.Name
            Status       = $State.Status
            ComputerName = $State.PSComputerName
        }
    }
}

Get-ServiceStatus -ServiceName $ServiceName -Server1 $Server1 -Server2 $Server2

Is it possible to have these outputs from the function to be separate to the function to use late on, please?:

Write-Host "1= "${Server1 State}
Write-Host "2= "${Server2 State}

If you want to save something for later use you save it in a variable. Instead of the second code block I posted before you use

$StateBefore = Get-ServiceStatus -Service 'W32Time', 'Spooler' -ComputerName 'PC01', 'PC02'

Now you can access the content of this variable again and again and again as often as you like. :wink:

Just out of curiousity - why do you still use 2 separate parameters for computer names in your function? Instead it would be a lot more flexible to use an array like I showed in my code suggestion.

Hi @Olaf, Sorry for my delay I have had to do some other more priority work and just popped back on this while I have a tiny bit of time before I have to go back to finish off that job.

Yes, I have used $StateBefore = before Get-ServiceStatus which captures both the results in 1 variable but bare in mind this is a completely automated script and any visual outputs are only for logs and not then for a person to action there and then, can I use that variable in a granular way later on in the script. i.e. IF $Server1 is running, stop it, if $Server2 is stopped, don’t do anything. Then later on only start up $Server1 as that was the only one started in the first place.

P.s. I have now used it in an array as you mentioned so I can easily have a different number of servers if required. Would that also help get the results out per server so I can used them per server later on?

Kind regards
Adam

I don’t get that point. What’s the problem with it?

Of course. You can use it for whatever you like.

I’m not sure what you’re actually asking but I’d say “yes”. :wink: If you want to use something later on, you have to save it in some way. An the default way to save a collection of something in PowerShell is an array. PowerShell even creates them implicitly for you when you output more than one element to a variable.

Hi @Olaf

I was suggesting that I was only getting a visual output and I wanted to know how to use those results later on in the script performing separate tasks on each server depending on those results.

I continued to look, but correct me if I’m wrong, I gather the best way to start/stop a process later in the script, depending on the results gathered, would be to use a line similar to:

foreach ($State in $StateBefore) {
    If ($State.Status -eq 'Running') {
        Write-Host $State.Name "was running on" $State.ComputerName #Or do something with it
    } else {Write-Host $State.Name "was stopped on" $State.ComputerName #So don't so anything with it}
}

Many thanks for your help and I hope I have seen a way on how to perform separate actions based on the results, which if I have I will work with what I have got before this goes off topic of what the initial title was asking.

Kind Regards
Adam

I actually answered that. You may read my second answer again.

If you saved the state of only ONE service of ONE server in the variable $StateBefore you’d be right. If you have more than one service and / or more than server you should rather use a loop iterating over an array of states and / or servers and treat each single one as needed.

Many thanks to @Olaf for the help and knowledge. The complete script I used in case anyone finds it helpful is below:

Variables Already Set from reading a separate .xml file. Sometimes “localhost” may be used so it can be used on multiple machines and some times there will not be a “SVR02” depending on the environment:

$ServiceName=“MyWindowsService”
$Server1=“Localhost”
$Server2=“SVR02”


The Script

Function to get the named service state on all the computers\servers involved. A “scope:” is used to allow it to work in my script, may not be required in this simplified version.

Function Get-ServiceStatus {
param (
$ServiceName,
$Servers
)

$StateList =
Invoke-Command -ComputerName $Servers -ScriptBlock {
    Get-Service $ServiceName
}

foreach ($Script:State in $StateList) {
    [PSCustomObject]@{
        Name         = $State.Name
        Status       = $State.Status
        ComputerName = $State.PSComputerName
        StartType    = $State.StartType
    }
}

}

Set the $Servers as an array Servers for flexibility, if required. If "Localhost” is set, convert that to the localhosts actual name.

$Servers = @(
    $Server1 -replace "Localhost", "$env:COMPUTERNAME"
    $Server2 -replace "Localhost", "$env:COMPUTERNAME"
)

If less servers are added to the array above, make a new array with the actual number of servers set because we can’t have a empty or null value in the array to invoke etc.

$ParsedServers = $Servers.Where({! [string]::IsNullOrWhiteSpace($_)})

Get the services status on the different servers using the new array without any empty or null values, calling the function set above.

$StateBefore = Get-ServiceStatus -ServiceName $ServiceName -Servers $ParsedServers

Stop the Service(s) and output on each server.

foreach ($Script:State in $StateBefore) {
Write-Host “Stopping Service…`t” -NoNewline -ForegroundColor Black
## If state isn’t Stopped on the remote server, Stop it.
If ($State.Status -ne ‘Stopped’) {
Invoke-Command -ComputerName $State.ComputerName -ScriptBlock {
Stop-Service $Using:ServiceName
}

    # Get ExecutionStatus of the last operation (*not part of this script so the function is not shown above, but I made a function to get me the success of the output*)
    Get-ExecutionStatus $?

 } else {
    Write-Host "`"$ServiceName`" already stopped!" -ForegroundColor Black
}

}

Starting Broker Servives

foreach ($Script:State in $StateBefore) {
Write-Host “Starting Solver Broker…`t” -NoNewline -ForegroundColor Black
## If state wasn’t Stopped in the first place or should have been started (I.e. Was meant to have automatically started and be running already), Start it on the remote server.
If ($State.Status -ne ‘Stopped’ -or $State.StartType -eq ‘Automatic’) {
Invoke-Command -ComputerName $State.ComputerName -ScriptBlock {
Start-Service $Using:ServiceName
}

    # Get ExecutionStatus of the last operation
    Get-ExecutionStatus $? (not part of this script so the function is not shown above, but I made a function to get me the success of the output)

  } else {
    Write-Host "`"$ServiceName`" Not set to run!" -ForegroundColor Black
}

}