I have a script that will stop and start services. However, I've had a few issues where services aren't stopping correctly which requires user intervention on the server. Before the start services portion of the script runs I'm using an if statement to check if all the services listed have stopped and if they haven't the script will pop an alert.
Below is a snippet of the script that does this. I'm trying to list the services in the pop-up with their status and start-up type but for some reason I'm not getting the list return. Using the line that is currently commented out I'm getting the service names returned but it's in a string.
#$RunningServices = (Get-Service -ComputerName $targetServer | Where-Object {($_.Name -like '*serviceAgroup*'-or $_.Name -like '*serviceBgroup*') -and $_.Status -eq "Running"})
$RunningServices = (Get-Service -ComputerName $targetServer |
Where-Object {($_.Name -like '*serviceAgroup*' -or $_.Name -like '*serviceBgroup*') -and $_.Status -eq "Running"}) |
select DisplayName, Status, StartType |
sort DisplayName;
Format-Table;
$pop = New-Object -ComObject WScript.Shell
$pop.Popup("Log onto $targetserver and stop Running serviceAgroup and serviceBgroup.`r`n$RunningServices", 0, "Services have not stopped!", 1)
The pipeline whose output you assign to the variable $RunningServices ends at sort DisplayName. The output is not passed into Format-Table. If you put a variable with an object (or an array of objects) into a string, the objects are expanded to their respective string representation.
Demonstration:
PS C:\> $s = Get-Service | Select-Object -First 3
PS C:\> $s
Status Name DisplayName
------ ---- -----------
Stopped AeLookupSvc Application Experience
Stopped ALG Application Layer Gateway Service
Stopped AppIDSvc Application Identity
PS C:\> "$s"
AeLookupSvc ALG AppIDSvc
PS C:\> _
For services the string representation is the value of the Name property, as you can see above. With your property selection (without the property Name) the result should be an empty string instead:
PS C:\> $s = Get-Service | Select-Object DisplayName,Status,StartType -First 3
PS C:\> $s
DisplayName Status StartType
----------- ------ ---------
Application Experience Stopped Manual
Application Layer Gateway Service Stopped Manual
Application Identity Stopped Manual
PS C:\> "$s"
PS C:\> _
To get the table output as a string you must pipe your object list not only through Format-Table, but also through Out-String. That's because Format-Table doesn't generate string output, but a list of format objects. Out-String converts these into actual string output.
PS C:\> $s1 = $s | Format-Table
PS C:\> "$s1"
Microsoft.PowerShell.Commands.Internal.Format.FormatStartData Microsoft.PowerShe
ll.Commands.Internal.Format.GroupStartData Microsoft.PowerShell.Commands.Interna
l.Format.FormatEntryData Microsoft.PowerShell.Commands.Internal.Format.FormatEnt
ryData Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData Microsoft.P
owerShell.Commands.Internal.Format.GroupEndData Microsoft.PowerShell.Commands.In
ternal.Format.FormatEndData
PS C:\> $s2 = $s | Format-Table | Out-String
PS C:\> "$s2"
DisplayName Status StartType
----------- ------ ---------
Application Experience Stopped Manual
Application Layer Gateway Service Stopped Manual
Application Identity Stopped Manual
But even with that your output probably won't be what you expect. Unlike the PowerShell console GUI dialogs use a proportional font, meaning that not all characters have the same width. Because of that something that looks like a proper table when using a monospace font will most likely look rather deformed when using a proportional font. Outputting the above string in a GUI dialog like this:
PS C:\> Add-Type -Assembly 'System.Windows.Forms'
PS C:\> [Windows.Forms.Messagebox]::Show($s2)
should present you with something like this:
To get an at least somewhat presentable result I'd suggest using custom format strings:
PS C:\> $s3 = $s | ForEach-Object {"{0}`t{1}`t{2}" -f $_.StartType,$_.Status,$_.DisplayName} | Out-String
PS C:\> [Windows.Forms.Messagebox]::Show($s3)
That should produce somewhat more presentable output:
The above takes advantage of the fact that the values of the StartType and Status properties don't vary too much in length, so the columns can be aligned reasonably well by separating the values with tab characters. If you must have the service names on the left side formatting becomes a lot more complicated, because you need to insert a variable amount of tabs depending on the number and width of the characters in the names. I'm going to leave that as an exercise for the reader.
Bottom line: Change your code to something like this:
Add-Type -Assembly 'System.Windows.Forms'
$svc = Get-Service -ComputerName $targetServer | Where-Object {
($_.Name -like '*serviceAgroup*' -or $_.Name -like '*serviceBgroup*') -and
$_.Status -eq 'Running'
} | Select-Object DisplayName,Status,StartType
$str = $svc | Sort-Object DisplayName | ForEach-Object {
"{0}`t{1}`t{2}" -f $_.StartType,$_.Status,$_.DisplayName
} | Out-String
[Windows.Forms.Messagebox]::Show($str)
and it should do (more or less) what you want.
Addendum: A better option for displaying the properties of a list of objects to the user would be a gridview:
$svc = Get-Service -ComputerName $targetServer | Where-Object {
($_.Name -like '*serviceAgroup*' -or $_.Name -like '*serviceBgroup*') -and
$_.Status -eq 'Running'
} | Select-Object DisplayName,Status,StartType
$svc | Out-GridView -Title 'Services'
$($RunningServices.DisplayName -join "`r`n")
Related
I am trying to create a basic script that pulls a list of computer names from a text file, then pings them, and returns true or false. I then want to output the ones that returned false to a text file so I can know which ones are not responding.
the closest I have got to what I want to is below:
$workstations = Get-Content "workstation_list.txt"
$workstations | Test-NetConnection -InformationLevel Quiet -WarningAction SilentlyContinue
However whenever I try to pipe the results anywhere all I get is the true or false.
How can I pass the original names that were in the $workstations array to show for all the ones that return false?
I have tried:
$workstations = Get-Content "workstation_list.txt"
$workstations |
Test-NetConnection -InformationLevel Detailed -WarningAction SilentlyContinue |
Select-Object computername, pingsucceeded |
if(pingsucceeded -eq False){write-output} else{continue}
with the following error:
pingsucceeded : The term 'pingsucceeded' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included,
verify that the path is correct and try again.
At line:11 char:144
+ ... Select-Object computername, pingsucceeded | if(pingsucceeded -eq Fal ...
+ ~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (pingsucceeded:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException*
However I can't figure out how to only return the original name of the computer that is returning false when I ping it.
I then want to output it to a text file, however if I can't get it to pass the correct information to the screen It doesn't go to a file either.
Am I close or do I need to approach this a completely different way?
Thanks!
PS.this is one of my first times posting a question on stack overflow, if I need to provide information in a different way to make it easier for you to answer please provide constructive feedback so I can do better in the future.
I'd recommend using a PSCustomObject to store your results like this:
$workstations = Get-Content "workstation_list.txt"
$Result =
foreach ($ComputerName in $workstations) {
[PSCustomObject]#{
ComputerName = $ComputerName
Online = (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet)
}
}
$Result
This way you can use the variable $Result for further steps if needed. Output the successful ones for example
$Result | Where-Object -Property 'Online' -EQ -Value $true
Or filter the unsuccessful ones and output them to another file for example:
$Result |
Where-Object -Property 'Online' -EQ -Value $false |
Select-Object -ExpandProperty ComputerName |
Out-File -FilePath 'offline_workstation_list.txt'
There's some basic powershell that you need to learn. You can't pipe to an if statement for one thing, but you can to foreach-object:
$workstations = Get-Content "workstation_list.txt"
$workstations |
Test-NetConnection -InformationLevel Detailed -WarningAction SilentlyContinue |
Select-Object computername, pingsucceeded |
foreach-object { if($_.pingsucceeded -eq $False){write-output $_} else{continue} }
ComputerName PingSucceeded
------------ -------------
microsoft.com False
Trying something with the call operator and $input.
echo hi | & { if ($input -eq 'hi') { 'yes' } }
yes
Is it possible to get multiple properties back for a command within parentheses? Consider the following code.
$service = (get-service -name 'wuauserv')
$service.Name # I get the name property back
But what If I wanted to get more than one property. For example below:
$service.Name,Status
Why doesn't this work? Is there a way to do it?
What you are dealing with is the concept of how PowerShell handles outputting information and the pipeline. When you collect Get-Service into the variable $service, you are storing an object that has multiple properties.
To work with the properties of a given object (one or many) you utilize Select-Object (docs). Whether you are dealing with that variable contents or directly with the output from Get-Service you have to pipe the output to Select-Object to retrieve one to many properties. You can do this multiple ways. PowerShell works on positions when it comes to parameters, so the position 0 parameter for Select-Object is -Property. All of the following are equivalent, and just various ways to get the same result:
$proc = Get-Service -Name mssql*
$proc | Select-Object Name, DisplayName
$proc = Get-Service -Name mssql*
Select-Object -Property Name, DisplayName -InputObject $proc
Get-Service -Name mssql* | Select-Object Name, DisplayName
If you want your variable to only contain a given set of properties then you would also utilize Select-Object:
$proc = Get-Service -Name mssql* | Select-Object Name, DisplayName
$proc
Output Example:
When you do $Service.Name it returns a the expanded property, how would you return the expanded property for multiple properties?
PS H:\> $Service = (Get-Service -name 'wuauserv')
PS H:\> $Service | Select-Object Name, Status
Name Status
---- ------
wuauserv Stopped
Notice how they are not an expanded property.
PS H:\> $service.Name
wuauserv
PS H:\> $service | Select-Object -ExpandProperty Name
wuauserv
Trying to expand multiple properties will end up with an error as the method cannot accept multiple arguments (Name, Status).
I have a script that will stop and start services. However, I've had a few issues where services aren't stopping correctly which requires user intervention on the server. Before the start services portion of the script runs I'm using an if statement to check if all the services listed have stopped and if they haven't the script will pop an alert.
Below is a snippet of the script that does this. I'm trying to list the services in the pop-up with their status and start-up type but for some reason I'm not getting the list return. Using the line that is currently commented out I'm getting the service names returned but it's in a string.
#$RunningServices = (Get-Service -ComputerName $targetServer | Where-Object {($_.Name -like '*serviceAgroup*'-or $_.Name -like '*serviceBgroup*') -and $_.Status -eq "Running"})
$RunningServices = (Get-Service -ComputerName $targetServer |
Where-Object {($_.Name -like '*serviceAgroup*' -or $_.Name -like '*serviceBgroup*') -and $_.Status -eq "Running"}) |
select DisplayName, Status, StartType |
sort DisplayName;
Format-Table;
$pop = New-Object -ComObject WScript.Shell
$pop.Popup("Log onto $targetserver and stop Running serviceAgroup and serviceBgroup.`r`n$RunningServices", 0, "Services have not stopped!", 1)
The pipeline whose output you assign to the variable $RunningServices ends at sort DisplayName. The output is not passed into Format-Table. If you put a variable with an object (or an array of objects) into a string, the objects are expanded to their respective string representation.
Demonstration:
PS C:\> $s = Get-Service | Select-Object -First 3
PS C:\> $s
Status Name DisplayName
------ ---- -----------
Stopped AeLookupSvc Application Experience
Stopped ALG Application Layer Gateway Service
Stopped AppIDSvc Application Identity
PS C:\> "$s"
AeLookupSvc ALG AppIDSvc
PS C:\> _
For services the string representation is the value of the Name property, as you can see above. With your property selection (without the property Name) the result should be an empty string instead:
PS C:\> $s = Get-Service | Select-Object DisplayName,Status,StartType -First 3
PS C:\> $s
DisplayName Status StartType
----------- ------ ---------
Application Experience Stopped Manual
Application Layer Gateway Service Stopped Manual
Application Identity Stopped Manual
PS C:\> "$s"
PS C:\> _
To get the table output as a string you must pipe your object list not only through Format-Table, but also through Out-String. That's because Format-Table doesn't generate string output, but a list of format objects. Out-String converts these into actual string output.
PS C:\> $s1 = $s | Format-Table
PS C:\> "$s1"
Microsoft.PowerShell.Commands.Internal.Format.FormatStartData Microsoft.PowerShe
ll.Commands.Internal.Format.GroupStartData Microsoft.PowerShell.Commands.Interna
l.Format.FormatEntryData Microsoft.PowerShell.Commands.Internal.Format.FormatEnt
ryData Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData Microsoft.P
owerShell.Commands.Internal.Format.GroupEndData Microsoft.PowerShell.Commands.In
ternal.Format.FormatEndData
PS C:\> $s2 = $s | Format-Table | Out-String
PS C:\> "$s2"
DisplayName Status StartType
----------- ------ ---------
Application Experience Stopped Manual
Application Layer Gateway Service Stopped Manual
Application Identity Stopped Manual
But even with that your output probably won't be what you expect. Unlike the PowerShell console GUI dialogs use a proportional font, meaning that not all characters have the same width. Because of that something that looks like a proper table when using a monospace font will most likely look rather deformed when using a proportional font. Outputting the above string in a GUI dialog like this:
PS C:\> Add-Type -Assembly 'System.Windows.Forms'
PS C:\> [Windows.Forms.Messagebox]::Show($s2)
should present you with something like this:
To get an at least somewhat presentable result I'd suggest using custom format strings:
PS C:\> $s3 = $s | ForEach-Object {"{0}`t{1}`t{2}" -f $_.StartType,$_.Status,$_.DisplayName} | Out-String
PS C:\> [Windows.Forms.Messagebox]::Show($s3)
That should produce somewhat more presentable output:
The above takes advantage of the fact that the values of the StartType and Status properties don't vary too much in length, so the columns can be aligned reasonably well by separating the values with tab characters. If you must have the service names on the left side formatting becomes a lot more complicated, because you need to insert a variable amount of tabs depending on the number and width of the characters in the names. I'm going to leave that as an exercise for the reader.
Bottom line: Change your code to something like this:
Add-Type -Assembly 'System.Windows.Forms'
$svc = Get-Service -ComputerName $targetServer | Where-Object {
($_.Name -like '*serviceAgroup*' -or $_.Name -like '*serviceBgroup*') -and
$_.Status -eq 'Running'
} | Select-Object DisplayName,Status,StartType
$str = $svc | Sort-Object DisplayName | ForEach-Object {
"{0}`t{1}`t{2}" -f $_.StartType,$_.Status,$_.DisplayName
} | Out-String
[Windows.Forms.Messagebox]::Show($str)
and it should do (more or less) what you want.
Addendum: A better option for displaying the properties of a list of objects to the user would be a gridview:
$svc = Get-Service -ComputerName $targetServer | Where-Object {
($_.Name -like '*serviceAgroup*' -or $_.Name -like '*serviceBgroup*') -and
$_.Status -eq 'Running'
} | Select-Object DisplayName,Status,StartType
$svc | Out-GridView -Title 'Services'
$($RunningServices.DisplayName -join "`r`n")
I have this script that changes services per a csv file input
Import-CSV .\SSAS_services.csv |
ForEach-Object{
Get-Service $_.Service -ComputerName $_.Server -PipelineVariable svc|
Set-Service -Status $_.Task -StartupType $_.'Startup Type' -PassThru
} |
Select-Object MachineName, Name, Status, StartType, #{n='OldStatus';e={$svc.Status}}, #{n='OldStartType';e={$svc.StartType}} |
tee-object -FilePath '.\ChangeServices_LOG.txt' #-Append
Server,Service,Startup Type,Task
DCVPIM108,SQL Server Analysis Services (MSSQLSERVER),automatic,start
server2,"SQL Server Analysis Services (MSSQLSERVER), SQL Server Analysis Services (MSSQLSERVER) CEIP",Manual,stop
it works great, except for my -PipelineVariable svcis not working as intended. if a service was "stopped" and "Manual" before being changed to "running" and "automatic", it doesnt get the old values "stopped" and "Manual" for OldStatus and OldStartType
MachineName : DCVPIM108
Name : MSSQLServerOLAPService
Status : Running
StartType : Automatic
OldStatus : Running
OldStartType : Automatic
why is that?
The -PipelineVariable / -pv common parameter only works:
within a single pipeline.
in script blocks in later segments of the same pipeline.
Since you're using it in a pipeline that is nested inside the ForEach-Object script block, the commands in the outer pipeline cannot use it.
However, I suggest restructuring your command so that you don't need a pipeline variable for Get-Service anymore.
Instead,
-PipelineVariable $csvRow is used with Import-Csv, so that you can more easily refer to it even in nested pipelines (the alternative would be to define the variable explicitly at the start of the ForEach-Object script block as $csvRow = $_).
$svc is then declared as an -OutVariable, so that the original service state is captured before Set-Service is called to change it.
Getting a service, setting its startup type, and enriching the CSV-row object with additional information now all happen inside the ForEach-Object script block.
Import-CSV .\SSAS_services.csv -PipelineVariable csvRow | ForEach-Object {
Get-Service -Name $csvRow.Service -ComputerName $csvRow.Server -OutVariable svc |
Set-Service -Status $csvRow.Task -StartupType $csvRow.'Startup Type'
$csvRow | Select-Object MachineName, Name, Status, StartType,
#{n='OldStatus';e={$svc.Status}},
#{n='OldStartType';e={$svc.StartType}}
} | Tee-object -FilePath '.\ChangeServices_LOG.txt'
I guess what you want is to pass same object down the multiple pipes. I haven't use -PipeLineVariable much, but looks like it just creating a nicer alias for $_ . If you need to push something specific down the pipeline I guess you need to use write-ouput with custom object or hashtable. Below is a dummy sample, pushing down and modifying a hastable:
$services = "xagt" , "xbgm" , "XblGameSave"
$list = new-object System.Collections.ArrayList
$serv | foreach {
$svc = Get-Service $_ ; Write-Output #{Name = $svc.Name; Stat=$svc.Status}
} | foreach {$_.SomeNewItem = "new stuff"; $list.Add($_)}
But in your case one pipeline might be sufficient. Try something like that:
Import-CSV .\SSAS_services.csv | foreach {
$old = Get-Service $_.Service;
Set-Service -Name $_.Service -Status Running
$new = Get-Service $_.Service;
$data = $_.MachineName, $_.Service, $old.Status, $new.Status -join ","
Write-Host $data
$data >> Log.txt
}
I want to check for specific services for a few specific servers and want the output to show on the same formatted table. I've only been able to create multiple tables, or to show only the last table formatted the way I want to. My intention is to show all on the same table.
Get-Service "ServiceA", "ServiceB", "ServiceC" -ComputerName SERVER1
Get-Service "ServiceD", "ServiceE", "ServiceF" -ComputerName SERVER2 |
Format-Table -Property MachineName, Status, Name, DisplayName -Auto
How do I include SERVER1 and SERVER2 on the same formatted table? The example above will only show me formatted table for the SERVER2?
Other way I've tried was to
Get-Service "ServiceA", "ServiceB", "ServiceC" -ComputerName SERVER1 |
Format-Table -Property MachineName, Status, Name, DisplayName -Auto
Get-Service "ServiceD", "ServiceE", "ServiceF" -ComputerName SERVER2 |
Format-Table -Property MachineName, Status, Name, DisplayName -Auto
but that way there's two different tables created, and not all the info in only one like i would like to.
I need to check differente services on six different servers, but with just two I think is enough to exemplify my difficulties on this script.
If you want them both to appear in one table, you need to send all of the results over to Format-Table at the same time. If you call Format-Table twice, you're going to get two separate tables.
Fortunately, PowerShell makes it really easy to store the results of a command in a variable, and then use that later.
What you need to do is make a variable to hold the results, and then store all of your Get-Service commands inside of it, like so:
#take the output and store it in $services
$services = get-service bits,winrm -computername ServerA
#add the output of the next to $services as well
$services += get-service AdobeARMservice,ALG -computername ServerB
#finally, make one big table to display it all
$services |Format-Table -Property MachineName, Status, Name, DisplayName -auto
MachineName Status Name DisplayName
----------- ------ ---- -----------
ServerA Running bits Background Intelligent Transfer Service
ServerA Running winrm Windows Remote Management (WS-Management)
ServerB Running AdobeARMservice Adobe Acrobat Update Service
ServerB Stopped ALG Application Layer Gateway Service
Before you go too far down this rabbit hole, keep in mind that Format-Table is ONLY meant for viewing stuff in the console. You can't take a table made with FT and then send export it as a .csv, for instance. If you're OK with just viewing it in the console though, this should work.
You could do it like this:
# create array
$services = #()
# add items to array
$services += Get-Service spooler
$services += Get-Service wsearch
# format array
$services | Format-Table -Property MachineName, Status, Name, DisplayName -AutoSize
Or like this:
# create list
$services = New-Object System.Collections.ArrayList
# add items to list
$services.Add($(Get-Service spooler)) | Out-Null
$services.Add($(Get-Service wsearch)) | Out-Null
# display list
$services | Format-Table -Property MachineName, Status, Name, DisplayName -AutoSize
Instead of populating an array or ArrayList, you can simply use a sub-expression to collate both outputs to a single Format-Table:
$(
get-service "ServiceA", "ServiceB", "ServiceC" -computername SERVER1
get-service "ServiceD", "ServiceE", "ServiceF" -computername SERVER2
) | Format-Table -Property MachineName, Status, Name, DisplayName -auto
Your examples don't produce the desired results, because for the first example only the output of the second Get-Service goes into Format-Table, and for the second example two separate tables are created.
If you take a look at the documentation of the Get-Service cmdlet you'll notice that both the -Name and the -ComputerName take a string array as input, so if you want to check the same services on all computers you can simply do something like this:
$servers = 'SERVER1', 'SERVER2'
$services = 'ServiceA', 'ServiceB', 'ServiceC'
Get-Service $services -ComputerName $servers |
Format-Table -Property MachineName, Status, Name, DisplayName -AutoSize
If you want to check different services on each server I'd map the services to the servers with a hashtable
$services = #{
'SERVER1' = 'ServiceA', 'ServiceB', 'ServiceC'
'SERVER2' = 'ServiceD', 'ServiceE', 'ServiceF'
}
and run Get-Service in a ForEach-Object loop, like this:
$services.Keys | ForEach-Object {
Get-Service $services[$_] -ComputerName $_
} | Format-Table -Property MachineName, Status, Name, DisplayName -AutoSize
or like this:
$services.GetEnumerator() | ForEach-Object {
Get-Service $_.Value -ComputerName $_.Name
} | Format-Table -Property MachineName, Status, Name, DisplayName -AutoSize