Script not starting service or logging the action

I am writing a script that will be able to look through a list of services on a server and start any service that is not started but is set to automatic. I am able to get the script to create my log file but it does not seem to be starting the service nor is it updating the log file. Any help with this script will be greatly appreciated.

[CmdLetBinding()]
param(
	[Parameter(Mandatory=$False, HelpMessage="Path to a .csv or .txt file with a list of servers (no header)")]
    [string]$ServerListFile = $null,
    [Parameter(Mandatory=$False, HelpMessage="Production Environment")]
    [switch]$Prod = $null,
	[Parameter(Mandatory=$False, HelpMessage="Non-Production Environment")]
    [switch]$NonProd = $null,
	[Parameter(Mandatory=$False, HelpMessage="Name of a computer")]
	[array]$ComputerName = $null
)

# Check to see if a file was specified or if it was just a server name(s)
if ($ServerListFile)
{
    try
    {
        Test-Path $serverListFile -ErrorAction Stop | Out-Null
    }
    catch
    {
        $ErrorMessage = "[ERROR] Missing Server List file -serverListFile $serverListFile"
        Write-Host "$ErrorMessage"
        exit
    }
    
    # Make sure you can import server names from the server list file.
    try
    {
        $ListOfServers = Get-Content -path $ServerListFile -ErrorAction Stop
    }
    catch
    {
        $ErrorMessage = "[ERROR] Error importing server list from file: $($ServerListFile)"
        exit
    }
}
elseif ($ComputerName)
{
    $ListOfServers = $ComputerName    
}
else
{
    $ListOfServers = $env:COMPUTERNAME
}

if($Prod){
    $LogFolder = "E:\Automation\Results\Services"
    $Exists = Test-Path -Path $LogFolder
    if ($Exists -eq "True") {
        Write-Host "Directory Exists!"
    } else {
        New-Item -Path $LogFolder -ItemType directory
    }

    $logdate = (Get-Date -UFormat ".%a %m-%d-%Y %H-%M %p")
    $LogserverFile = "\\ProdSErver\E$\Automation\Results\Services\Automatic Service Check Results$logdate.log"
    New-Item -Path $LogserverFile -ItemType File
}

if($NonProd){
    $LogFolder = "E:\Automation\Results\Services"
    $Exists = Test-Path -Path $LogFolder
    if ($Exists -eq "True") {
        Write-Host "Directory Exists!"
    } else {
        New-Item -Path $LogFolder -ItemType directory
    }

    $logdate = (Get-Date -UFormat ".%a %m-%d-%Y %H-%M %p")
    $LogserverFile = "\\NonProdServer\E$\Automation\Results\Services\Automatic Service Check Results$logdate.log"
    New-Item -Path $LogserverFile -ItemType File
}

function Get-TimeStamp {
    
    return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)
    
}

Foreach ($server in $ListOfServers){
    Invoke-Command -ComputerName $Server -ScriptBlock{
        function Get-TimeStamp {    
            return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)            
        }
	    $Services = get-service | Select-Object name -ExpandProperty name
        foreach ($Service in $services){
            if (($Service.StartType -like "Automatic") -AND ($service.status -like "Stopped")){
                Start-Service -name $Service
                Add-Content -path $LogserverFile -Value "$(Get-TimeStamp) Service $Service has been started on server $Using:Server."
            }
        }
    }
}

Using PowerShell is about working with rich and powerful objects. With the following line of code you destroy all this richness and end up with a list of stupid boring strings with the names of the services:

$Services = get-service | Select-Object name -ExpandProperty name

Inside the loop then you try to access the .StartType and .Status property you just removed two lines above.

if (($Service.StartType -like "Automatic") -AND ($service.status -like "Stopped")) {

That won’t work. :wink:

So please remove the Select-Object completely.

And the log isn’t defined in the remote code block. And even if it was OP would hit the double hop issue

Olaf,

I took your advice to a degree. Instead of getting rid of my select-object, I added in a new variable that uses get-service against the $service variable.

Invoke-Command -ComputerName $Server -ScriptBlock{
        function Get-TimeStamp {    
            return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)            
        }
	    $Services = get-service | Select-Object name -ExpandProperty name
        foreach ($Service in $services){
            $CheckService = Get-Service -name $Service
            if (($CheckService.StartType -like "Automatic") -AND ($CheckService.status -like "Stopped")){
                Start-Service -name $Service
                Add-Content -path $using:LogserverFile -credential $Creds -Value "$(Get-TimeStamp) Service $Service has been started on server $Using:Server."
            }
        }
    }

The only problem I am having is the add-content to my log file. Would it make sense to add the started service to an array and just use that array to fill in what service were started in the logs and move the add-content to the beginning Foreach loop? My only issue with that would be even if there were not services started on the server, there would be an entry in the log file.

Very clever. Now you query each service twice. :smirk:

Doug had already pointed out this problem. To circumvent the double hop problem you could simply output whatever you like to log to the console and collect the whole output of the Invoke-Command in a variable.

Another way would be to get rid of the Invoke-Command part and use implicit remoting of Get-Service.

The best option would be to fix your services. A proper service shouldn’t need such circumstances. At least not on a regular base.

Olaf,

When I look at the command Get-Service, there is not a switch I can use to have it connect to another computer. This is why I was going the route of using Invoke-Command. If you know of a better way to do this, please let me know. The primary reason for this script is actually for running the script after a reboot of the servers in the environment after a production deployment.

What version of PowerShell do you use? :wink: If it is Windows PowerShell version 3 or higher there is a parameter -ComputerName.

Define better! :wink:

In the end it is about you to figure out what’s the best way for you in your environment. I’d expect services to behave as designed. If they don’t I’d try to get them fixed by the vendor or creator of the software or solution they belong to.

Computername parameters usually depended on dcom and were removed from powershell core. But there also exists cimsessions to achieve the same result

Olaf and krzydoug,

Thank you both for your insights and your help. Below is the code that I am now using and seems to be working exactly how I wanted it to. I now only have it using the get-service cmdlet once and logging is showing exactly what I want. Thank you very much for your help.

$LogFolder = "E:\Automation\Results\Services"
$Exists = Test-Path -Path $LogFolder
if ($Exists -eq "True") {
    Write-Host "Directory Exists!"
} else {
    New-Item -Path $LogFolder -ItemType directory
}

$logdate = (Get-Date -UFormat ".%a %m-%d-%Y %H-%M %p")
$LogFile = New-Item -Path "$LogFolder\Automatic Service Check Results$logdate.log" -ItemType File

function Get-TimeStamp {
    
    return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)
    
}

Foreach ($server in $ListOfServers){
    function Get-TimeStamp {    
        return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)            
    }
    $Services = get-service -ComputerName $Server
    foreach ($Service in $services){
        if (($Service.StartType -like "Automatic") -AND ($Service.status -like "Stopped")){
            Start-Service -name $Service.name
            $Name = $service.displayname
            Add-Content -path $LogFile -Value "$(Get-TimeStamp) Service $Name has been started on server $Server."
        }
    }
}

That is much better! Good job

Great. I’m glad we could help. :+1:t4: :slightly_smiling_face:

This is just a nitpick, as you’ve solved your problem and your code looks much better.
However you define the function Get-TimeStamp twice in the code you’ve posted.

I would remove the version inside the Foreach-loop as it runs once for each server and depending on the number of servers you’re querying it takes unneeded time from the runtime of the loop.

1 Like

In this case - as the function is actually just one line of code - I would go a step further and would use the snippet

(Get-Date).ToString('[yyyy/MM/dd HH:mm:ss]')

instead of a function. :wink:

Thank you for pointing that out, I forgot to remove that when I killed the Invoke-Command section. As for Olaf’s suggestion, the function is built into all of our scripts we write specifically for our logging. This is why I have it built as a function instead of just a code snippet. If there is a better way to do this, I would be glad to take a look at it and implement it in my code.

As Olaf says it’s a bit overkill running it as a function.
What I would do instead is create a variable below your $logdate and $logfile lines like this:

$TimeStamp = (Get-Date).ToString('[yyyy/MM/dd HH:mm:ss]')

And then just call that variable when you need it:

Add-Content -path $LogFile -Value "$TimeStamp Service $Name has been started on server $Server."

Update
Depending on how important it is for you to log exactly when each server was queried the above will not work as it sets the variable once you start the script and it will not change over the runtime of the script.

Embedding the snippet as is from Olaf directly into the needed line will of course work as it’s evaluated every time that line runs:

Add-Content -path $LogFile -Value "$((Get-Date).ToString('[yyyy/MM/dd HH:mm:ss]')) Service $Name has been started on server $Server."

That being said the function call does the same and takes less space on the Add-Content line so I’m not sure that a change is really necessary.