Get multiple fields back? - powershell

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).

Related

Listing services and status in a pop-up

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")

Getting format-table to work correctly with multiple sources

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

Azure PowerShell cmdlet into Strings

Just a quick question. I am running lines of code like,
$publicIP = Get-Content (Get-AzureVM -ServiceName $servicename -Name $vmsname | Get-AzureEndpoint | Select { $._vip })
$OSDisk = Get-Content (Get-AzureVM -ServiceName $servicename -Name $vmsname | Get-AzureOSDisk)
And it does get me the IPAddress or the variables, but when trying to put the value into a csv cell, it fails. I think it is because it tries to add the extra labels, but I do not know how to get just the string and set the variable as just that. Does anyone know how to remedy this?
edit:
If the output with:
Get-AzureVM -servicename "vm1" -name "vm1" | Select DNSName | Out-String
is like this:
DNSName
-------
http://example.cloudapp.net/
How do I just put in "http://example.cloudapp.net/" as a CSV-entry? Right now it is trying to put all of the code block into the CSV which is giving me awful formatting erros.
Select is an alias for Select-Object and allows you to take one or more properties from an object and creates a new object with them.
If you want to expand a property into just the value, use the -ExpandProperty parameter:
Get-AzureVM -servicename "vm1" -name "vm1" | Select -ExpandProperty DNSName
Should give you just http://example.cloudapp.net/
Also, when you want to send data to a CSV, you can pipe objects to the cmdlet Export-CSV which will use the property names as headers and the objects in the array as rows and the properties as columns.
Example:
Get-ChildItem -File | Select Name, Size | Export-Csv c:\temp\filelist.csv

Get-Service results to array

I'm sure this should be straight forward but I've been stuck on it for a while now...
I am trying to get the service names (for sql server) into an array but can't figure out how to do it. I basically want the array contents to look something like the output of this:
Get-Service -computername $server_name -name sql* | format-table -property name
I have tried things like this but the contents of $service_name are very odd:
$service_name = (Get-Service -computername $server_name -name sql* -exclude *sqlwriter | format-table -property name)
Whatever I try either errors or gives some strange message in my array. Should this be easy/possible? I'm guessing I could dump the results in a text file then use the contents of that but it's a bit messy and more overhead than seems necessary.
Not sure, but where you looking for this (with whatever variation on the pattern for -Name and/or -Exclude)?
PS> $names = (Get-Service -Name Sql* | select name)
PS> $names.GetType().IsArray
True
PS> $names.Length
3
PS> $names
Name
----
SQLBrowser
SQLSERVERAGENT
SQLWriter
Or even along the lines of the following if you really want the "Name" as a System.String array.
PS> $names = (Get-Service -Name Sql* | foreach { $_.Name -as [string]})
PS> $names[0].GetType().FullName
System.String
PS> $names
SQLBrowser
SQLSERVERAGENT
SQLWriter
But also keep the good advice in #alroc's answer in mind - maybe you want to keep the actual type of Get-Service's result (System.ServiceProcess.ServiceController) as long as possible and access/use the Name property of it as late as possible. YMMV.
You're using format-table in the pipeline. Anytime you use a format-* cmdlet, that's the end of the line for your data - it's now just a formatted bunch of text, it's no longer data that you can actually use.
Try this to get the names formatted as a table for viewing:
$service_name = (Get-Service -computername $server_name -name sql* -exclude *sqlwriter);
$service_name | format-table -property name;
That said, I ran both your version and mine above and got the same visual output - the critical difference is that mine stores data in $service_name, not just a bunch of text (as yours does).
edit:
In response to:
All I really needed was the service names in an array to then use elsewhere
$service_name = (Get-Service -computername $server_name -name sql* -exclude *sqlwriter) | select-object -expandproperty name;
or:
$service_name = (Get-Service -computername $server_name -name sql* -exclude *sqlwriter).name;
No need to invoke WMI directly.

Formatting the results of a powershell script

How can i add the server name at the left of each line result on this script?. Thank you!
$servers = Get-Content -path .\Machines.txt
[pscustomobject]$result = #()
$subresult =
ForEach ($server in $servers)
{
Set-Service -computername $servers -Name sacsvr -StartupType Disabled -PassThru
}
$result = $subresult
$result | Out-File local_group_members.csv
This is an example result:
Status Name DisplayName
------ ---- -----------
Stopped sacsvr Special Administration Console Helper
Stopped sacsvr Special Administration Console Helper
Stopped sacsvr Special Administration Console Helper
Alternatively you can just add a property to the objects you're outputting right now. Pipe your Set-Service to Add-Member like this:
Set-Service -computername $servers -Name sacsvr -StartupType Disabled -PassThru | Add-Member -MemberType NoteProperty -Name 'Server' -Value $Server -PassThru
Now each object that you pass to $subresult has a new property Server that is the name of the server it was run on. You'll probably want to pipe through Select when outputting to have the order you want.
$SubResult | Select Server, Status, Name, DisplayName | Export-CSV 'local_group_members.csv' -NoType
You can arbitrarily re-order or add to your output with Select-Object. You can use hash tables to include calculated properties such as your desired ServerName.
So for each server, you can set the services and tag the output with that server name:
ForEach ($server in $servers)
{
Set-Service -computername $server -Name sacsvr -StartupType Disabled -PassThru |
Select #{Name = 'ServerName'; Expression = {$server}}, Name, DisplayName, Status
}
The above is shorthand for:
Select-Object -Property (properties)
The -Property parameter allows you to select any arbitrary grouping of properties on the type of object being piped in. Another parameter, -InputObject allows us to pipe in objects by value.