I am working on simple reporting one-liner and I can't get it to work.
I am retrieving the ESXi hosts by cmdlet Get-VMHost. Get-VMHost is piped into Get-VMHostSysLogServer.
As the output I get the Host and Port properties. I would like to display the following properties:
Name (from Get-VMHost)
Host and Port (both from Get-VMHostSysLogServer).
How can I achieve this?
Here's one solution, that builds a custom object with the properties that you want:
Get-VMHost | ForEach-Object {
$SysLog = $_ | Get-VMHostSysLogServer
ForEach ($SysLogServer in $SysLog) {
$Result = #{
VMHost = $_.name
SysLogHost = $SysLogServer.Host
Port = $SysLogServer.Port
}
New-Object -TypeName PSObject -Property $Result
}
}
Explanation:
Uses a ForEach-Object loop to iterate through each VM Host returned by Get-VMHost (represented as the current pipeline item via $_).
Gets the SysLogServers of each Host in to $SysLog
Loops through all SysLogServers, building a hashtable #{ } of the desired properties.
Uses New-Object to output an object using the properties in the Hashtable which is returned to the Pipeline.
If you'd like to capture the results to a variable, just add $YouVar = before Get-VMHost.
If you want to send on the results to another cmdlet that accepts pipeline input (such as Export-CSV) you can do that directly as the end, just append | Export-CSV Your.csv after the last closing }. This is the benefit of using ForEach-Object as the outer loop, it supports the pipeline.
Related
I keep running into the same problem again, and i have my default way of handling it, but it keeps bugging me.
Isn't there any better way?
So basicly i have a pipline running, do stuff within the pipline, and want to return a Key/Value Pair from within the pipline.
I want the whole pipline to return a object of type psobject (or pscustomobject).
Here is the way i do it everytime.
I create a hashtable at the beginning of the pipline and add key/Value Pairs from within the pipline to this hashtable using the .Add() method.
Afterwards i create a psobject by passing the hashtbale to New-Object`s -Property Parameter. This gives me the desired result.
Get-Process | Sort -Unique Name | ForEach-Object -Begin { $ht = #{} } -Process {
# DO STUFF
$key = $_.Name
$val = $_.Id
# Add Entry to Hashtable
$ht.Add($key,$val)
}
# Create PSObject from Hashtable
$myAwesomeNewObject = New-Object psobject -Property $ht
# Done - returns System.Management.Automation.PSCustomObject
$myAwesomeNewObject.GetType().FullName
But this seems a bit cluncky, isn't there a more elegant way of doing it?
Something like this:
[PSObject]$myAwesomeNewObject = Get-Process | Sort -Unique Name | ForEach-Object -Process {
# DO STUFF
$key = $_.Name
$val = $_.Id
# return Key/Val Pair
#{$key=$val}
}
# Failed - returns System.Object[]
$myAwesomeNewObject.GetType().FullName
This unfortunally dosn't work, since the pipe returns an array of hashtables, but i hope you know now what iam trying to achieve.
Thanks
Not sure if this is more elegant but just another way of doing it, this uses an anonymous function so $ht will no longer be available after execution, and casts to [pscustomobject] instead of using New-Object:
[pscustomobject] (Get-Process | Sort -Unique Name | & {
begin { $ht = #{ } }
process {
# DO STUFF
$key = $_.Name
$val = $_.Id
# Add Entry to Hashtable
$ht.Add($key, $val)
}
end { $ht }
})
You can also use the -End parameter to convert the final hash table to a pscustomobject as part of the pipeline, without needing to set the whole thing to a variable
$ht[$key]=$val is also a nice shorthand for $ht.Add($key,$val):
Get-Process |
Sort -Unique Name |
Foreach -Begin { $ht = #{} } -Process {
$ht[$_.Name] = $_.Id
} -End {[pscustomobject]$ht} |
## continue pipeline with pscustomobject
Thanks to #Santiago Squarzon and #Cpt.Whale answers, i were able to combine them to create a solution that pleases me:
$myAwesomeNewObject = `
Get-Process | Sort -Unique Name | & {
begin { $ht = #{} }
process {
# DO STUFF
$key = $_.Name
$val = $_.Id
# Add Entry to Hashtable
$ht[$key]=$val
}
end {[pscustomobject]$ht}
}
# Success - System.Management.Automation.PSCustomObject
$myAwesomeNewObject.Gettype().FullName
# And helper Hashtable is NULL thanks to the
# anonym function
$null -eq $ht
Thanks alot Guys
Alternatively you may create a hashtable using Group-Object -AsHashTable:
# Store the PIDs of all processes into a PSCustomObject, keyed by the process name
$processes = [PSCustomObject] (Get-Process -PV proc |
Select-Object -Expand Id |
Group-Object { $proc.Name } -AsHashtable)
# List all PIDs of given process
$processes.chrome
Notes:
Common parameter -PV (alias of -PipelineVariable) makes sure that we can still access the full process object from within the calculated property of the Group-Object command, despite that we have a Select-Object command in between.
The values of the properties are arrays, which store the process IDs of all instances of each process. E. g. $processes.chrome outputs a list of PIDs of all instances of the chrome process.
Right now I'm running
Get-CimInstance -ComputerName $i.DNSHostName -Class CIM_Processor | Select-Object "Name", "NumberOfCores" | Export-Csv -Path .\test.csv -NoTypeInformation -Append`
This will put the CPU name and core count in a CSV fine but how can I add in the hostname as another column?
This is a good case for using a PsCustomObject, which allows you to dynamically create an object with arbitrary properties/values.
$cimProcessor = Get-CimInstance -ComputerName $hostName -Class CIM_Processor
$row = [PSCustomObject]#{
ComputerName = $hostName
Name = $cimProcessor.Name
NumberOfCores = $cimProcessor.NumberOfCores
}
$row | Export-Csv -Path test.csv -NoTypeInformation -Append
In the case where you want to retrieve information from a remote machine first, then you can skip the calculated property and simply select the PSComputerName property instead:
Note: The ellipses ... indicate the code before or after from your original sample.
... | Select-Object PSComputerName, Name, NumberOfCores | ...
Any cmdlet which connects to remote systems via WinRM should have this property automatically set when data is returned over a remote session.
If you are running this from a local session, you could use Select-Object to create a calculated property, then call the hostname command to populate its value:
... | Select-Object "Name", "NumberOfCores", #{
Name = 'ComputerName';
Expression = { hostname };
} | ...
This solution is often suitable for cross platform scripts since a hostname binary is available out of the box on Windows, MacOS, and most major distributions of Linux.
Explaining Calculated Properties
Calculated properties work by defining a hashtable in a specific format and providing the hashtable as a property to be computed just as you would use a string for a real property on the object:
$property = #{
Name = 'PropertyName';
Expression = { 'ScriptBlock' };
}
# Note that the hashtable can be specified inline as shown above
# or as a variable like shown here
[PSCustomObject]#{ Name = 'Bender'; Loves = 'Bending' } |
Select-Object Name, Loves, $property
Keep in mind that within the Expression ScriptBlock, $PSItem/$_ is set to the current object in the pipeline. Use this to reference static or instance properties from the current object you are selecting information from.
I have been researching the web to see what am I missing and can't find out, I run the command it goes thru the list of computers but the export doc is always empty.
Here is the code
foreach ($computer in Get-Content "\\NETWORK PATH\user-computers.txt") {
Write-host $computer
$colDrives = Get-WmiObject Win32_MappedLogicalDisk -ComputerName $computer
$Report = #()
# Set our filename based on the execution time
$filenamestring = "$computer-$(get-date -UFormat "%y-%b-%a-%H%M").csv"
foreach ($objDrive in $colDrives) {
# For each mapped drive – build a hash containing information
$hash = #{
ComputerName = $computer
MappedLocation = $objDrive.ProviderName
DriveLetter = $objDrive.DeviceId
}
# Add the hash to a new object
$objDriveInfo = new-object PSObject -Property $hash
# Store our new object within the report array
$Report += $objDriveInfo
}}
# Export our report array to CSV and store as our dynamic file name
$Report | Export-Csv -LiteralPath "\\NETWORK PATH\Drive-Maps.csv" -NoTypeInformation
I want to know what each computer currently got mapped network drives, thanks for all your help and guidance.
I'm not sure why you're not getting output. I've rewritten your script for a few reasons I'd like to point out. First, your variable naming is not very clear. I'm guessing you come from a VBScripting background. Next, you're creating an array and then adding to it - this is simply not needed. You can capture the output of any loop/scriptblock/etc directly by assigning like tihs.
$Report = foreach($thing in $manythings){Do lots of stuff and everything in stdout will be captured}
If you write your script in a way that takes advantage of the pipeline, you can do even more. Next, creating the object with New-Object is slow compared to using the [PSCustomObject] type accelerator introduced in V3. Finally, it seems you create a custom csv for each computer but in the end you just export everything to one file. I'm going to assume you are wanting to collect all this info and put in one CSV.
My recommendation for you to help troubleshoot, run this against your machines and confirm the output on the screen. Whatever you see on the screen should be captured in the report variable. (Except write-host, it's special and just goes to the console)
$computerList = "\\NETWORK PATH\user-computers.txt"
$reportFile = "\\NETWORK PATH\Drive-Maps.csv"
Get-Content $computerList | ForEach-Object {
Write-host $_
$mappedDrives = Get-WmiObject Win32_MappedLogicalDisk -ComputerName $_
foreach ($drive in $mappedDrives)
{
# For each mapped drive – build a hash containing information
[PSCustomObject]#{
ComputerName = $_
MappedLocation = $drive.ProviderName
DriveLetter = $drive.DeviceId
}
}
} -OutVariable Report
Once you know you have all the correct info, run this to export it.
$Report | Export-Csv -LiteralPath $reportFile -NoTypeInformation
So the output works fine but I'm having an issue with it only outputing the last line it runs. Is there anyway to check for loops to test in the future?
but i have a list of ip address and im trying to check if the firewall in windows is enabled or disabled.
They are on one LARGE (300+ workgroup). Any help in getting this to loop properly would be appreciated. Security and other things are not a concern cause i have other scripts that run fine. And i dont get any errors. just the single output.
ive already tried moving the array and that didn't help. im thinking it could be the PSCustomObject part as i'm just starting to learn these. Or could it be my input and output formats are different and that's causing issues??
clear
$ComputerList = get-content C:\Users\Administrator\Desktop\DavidsScripts\TurnOffFirewall\input.txt
$Status = #(
foreach ($Computer in $ComputerList) {
netsh -r $Computer advfirewall show currentprofile state})[3] -replace 'State' -replace '\s'
$Object = [PSCustomObject]#{
Computer = $Computer
Firewall = $Status
}
Write-Output $Object
$Object | Export-Csv -Path "C:\FirewallStatus.csv" -Append -NoTypeInformation
Your previous code was not escaping the loop and was only adding the last computer in the loop to the object.
The best way I have found, is to make a temp object and add it to an array list then export that. Much nicer.
$ComputerList = get-content C:\Users\Administrator\Desktop\DavidsScripts\TurnOffFirewall\input.txt
$collectionVariable = New-Object System.Collections.ArrayList
ForEach ($Computer in $ComputerList) {
# Create temp object
$temp = New-Object System.Object
# Add members to temp object
$temp | Add-Member -MemberType NoteProperty -Name "Computer" -Value $Computer
$temp | Add-Member -MemberType NoteProperty -Name "Firewall" -Value $((netsh -r $Computer advfirewall show currentprofile state)[3] -replace 'State' -replace '\s')
# Add the temp object to ArrayList
$collectionVariable.Add($temp)
}
Write-Output $collectionVariable
$collectionVariable | Export-Csv -Path "C:\FirewallStatus.csv" -Append -NoTypeInformation
Here's a streamlined, functional version of your code, using a single pipeline:
Get-Content C:\Users\Administrator\Desktop\DavidsScripts\TurnOffFirewall\input.txt |
ForEach-Object {
[pscustomobject] #{
Computer = $_
Firewall = (-split ((netsh -r $_ advfirewall show currentprofile state) -match '^State'))[-1]
}
} | Export-Csv -Path C:\FirewallStatus.csv -NoTypeInformation
Note:
No intermediate variables are needed; each computer name read from the input file is processed one by one, and each custom object constructed based on it is sent to the output CSV file.
The command for extracting the firewall status from netsh's output was made more robust in order to extract the state information based on the line content (regex ^State, i.e., a line starting with State) rather than a line index ([3]); the unary form of -split splits the line of interest into tokens by whitespace, and index [-1] extracts the last token, which is the state value.
As for what you tried:
Your foreach loop ended before $Object was constructed, so you ended up constructing just 1 object to send to the output file with Export-Csv.
If you had formatted your code properly, that fact would have been more obvious; try using Visual Studio Code with the PowerShell extension, which offers automatic formatting via the >Format Document (Shift+Alt+F) command.
I have the following powershell 1 liners that get me the results I'm looking for. Listing out the memberships of the local administrators group.
$LocalAdmins = $([ADSI]"WinNT://$Target/Administrators,group").psbase.Invoke('Members')
$Members = $LocalAdmins | foreach { $_.GetType().InvokeMember('ADspath', 'GetProperty', $null, $_, $null).Replace('WinNT://', '')} | sort -Descending
or this:
Net localgroup Administrators
When I attempt to pipe the results to ConvertTo-Html cmdlet it seems to just give me the -length property of each object in the pipeline.
Any ideas how to get to get this list to output properly in HTML?
ConvertTo-Html takes the properties of the given input object(s) and creates a HTML page to display these properties.
Since your foreach outputs just a bunch of strings, it takes the only non-standard property of string, which is Length.
If you want to see the string value in the output, too, you can try to add another property, like this
| foreach { Add-Member -InputObject $_ -NotePropertyName "Value" -NotePropertyValue "$_"; $_ } | ConvertTo-Html