Powershell capturing loop output to a variable - powershell

Hi guys im having issues getting my head around how I capture ping response to a variable if that makes sense. As i want to be able to output back to a csv with the response. Of course there is a very good chance im approaching this in totally the wrong way !
$PingMachines=import-Csv -path C:\temp\pcs.csv -Header cn,operatingsystem,LastLogonDate
foreach ($pc in $pingmachines.cn) {
$PingStatus = Gwmi Win32_PingStatus -Filter "Address = '$pc'" | `
Select-Object StatusCode
If ($PingStatus.StatusCode -eq 0){
Write-Host $pc "up"
}
Else {
Write-Host $pc "down"
}
}
In an ideal world id love to be able to save the output ie pc,pingstatus.statuscode back to a variable but im struggling with the logic and how to increment to the variable rather than just having the last object.
Thanks in advance.

Use the Win32_PingStatus WMI object, as it already contains the data you need. When you pipe gwmi result to Select-Object, you remove all but StatusCode.
Consider
$PingStatus = Gwmi Win32_PingStatus -Filter "Address = '$pc'" | Select-Object StatusCode
$PingStatus # Contains only StatusCode
Output:
StatusCode
----------
0
Whereas the WMI class contains more members:
$PingStatus = Gwmi Win32_PingStatus -Filter "Address = '$pc'"
$PingStatus # Contains a lot more
Output:
Source Destination IPV4Address IPV6Address Bytes Time(ms)
------ ----------- ----------- ----------- ----- --------
MyPC Server 10.0.0.1 {} 32 1

I rewrote your code a bit; this works for me:
$machines = import-csv -path machines.csv -header ip,os,LastLogonDate
foreach ($machine in $machines)
{
$ip = $machine.ip
$status = gwmi win32_PingStatus -filter "Address = '$ip'"
if ($status.StatusCode -eq 0)
{ Write-Host $ip 'up' }
else
{ Write-Host $ip 'down' }
}
I tested it out on a file machines.csv that looks like this:
"127.0.0.1","linux","2012-1-1"
"192.168.1.93","minux","2012-2-10"
"192.168.1.254","xenix","2012-3-20"
"192.168.1.66","dynix","2012-4-5"
When I run it, the output looks something like this:
PS C:\Users\dharmatech\Documents> C:\Users\dharmatech\Documents\check-machine-status.ps1
127.0.0.1 up
192.168.1.93 up
192.168.1.254 up
192.168.1.66 up

Use the pipeline with ForEach-Object instead of the foreach( in ) construct. Using ForEach-Object will run the commands as part of the pipeline, which will allow you to capture the output as a variable.
$PingMachines=import-Csv -path C:\temp\pcs.csv -Header cn,operatingsystem,LastLogonDate
$PingMachines.cn | ForEach-Object {
$PingStatus = Gwmi Win32_PingStatus -Filter "Address = '$_'" | `
Select-Object StatusCode
If ($PingStatus.StatusCode -eq 0){
Write-Host $_ "up"
}
Else {
Write-Host $_ "down"
}
}
You can think of the pipeline version's $_ automatic variable like this:
foreach( $_ in $PingMachines.cn) {
#code that uses $_
}
Once you have a pipeline going, you'll need to output an object instead of just printing to the screen using Write-Host:
$PingMachines=import-Csv -path C:\temp\pcs.csv -Header cn,operatingsystem,LastLogonDate
$PingResults = $PingMachines.cn | ForEach-Object {
$PingStatus = Gwmi Win32_PingStatus -Filter "Address = '$_'" | `
Select-Object StatusCode,Address
#I added the Address property above so you would have the machine name in the output object
If ($PingStatus.StatusCode -eq 0){
Write-Host $_ "up"
}
Else {
Write-Host $_ "down"
}
#Send the $PingStatus object out on the pipeline, which will end up in $PingResults
Write-Output $PingStatus
}
June Blender recently posted a good article on powershell.org that covers outputting objects vs Write-Host and creating custom objects so I won't go into the full detail here.

Related

System.String[] - Result

I did a search and I understand the issue that some of these are arrays. What I am not sure I understand is why I am getting this when I am specifically trying to return just one value.
I am using a ForEach loop and pulling from a text file the names of the computers I want to query.
I want to verify that the IP address I am pinging is the same computer I want to connect to. We have many users on VPN and the DNS doesn't update fast enough. This means we get some incorrect information.
$NIC2 = Get-WmiObject win32_NetworkAdapterConfiguration -ComputerName $computer | Where-Object {$_.DNSDomain -eq "usms.contoso.com"} | Select-Object IPAddress
This should give me only one IP address and not an array, but unfortunately I get the System.String[] in the CSV output. If I just type this command out in the PowerShell command line it works beautifully, but not when I put it into a CSV.
any ideas?
Thanks in advance.
Entire code below - I get the results for everything except the $NIC2 and doing $NIC2.IPAddress[0] doesn't seem to work.
Putting $NIC2[0] just give me this in that column
dapterConfiguration -ComputerName $computer | Where-Object {$_.DNSDomain -eq "contoso.com"} | Select-Object IPAddress
#Definitions of variables
#************************
#Defines the location and name of the text file from where computers will be pulled
$Computers = Get-Content "c:\Temp\computers.txt"
#Defines the variable and format for Date
$Date = Get-date -UFormat %h_%d_%y_%H%M
#Define the name of the files to output
$OutputFile = "C:\Temp\Verify-Computer-$Date.csv" #All Computers On-Line Status will be provided in this file
#Defines the Array where to put the data and the $Ping variable to store the IP address
$DataArray = #()
$Resolution = New-Object System.Object.NetworkInformation.Ping
#Define A Function that will test which computers are on-line and create files to use in the other function.
ForEach ($computer in $Computers)
{
#Defining the variables
$TESTConnection = $null
$Object = $null
$Value = $null
$IPAddress = $null
$NameResolution = $null
$PingAddress = $null
$NIC = $null
$NIC2 = $null
$NICIP = $null
# Ping computer
$TESTConnection = Test-NetConnection -ComputerName $computer | Select-Object PingSucceeded
#If connection is live Get IP address
IF ($TESTConnection.PingSucceeded -eq "True") {
$IPAddress = Test-NetConnection -ComputerName $computer -InformationLevel "Detailed" | Select-Object RemoteAddress
$NIC = Get-WmiObject win32_NetworkAdapterConfiguration -ComputerName $computer | Where-Object {$_.DNSDomain -eq "contoso.com"} | Select-Object MACAddress, DNSHostName
$NIC2 = {Get-WmiObject win32_NetworkAdapterConfiguration -ComputerName $computer | Where-Object {$_.DNSDomain -eq "contoso.com"} | Select-Object IPAddress}
$Value = $TESTConnection.PingSucceeded
$PingAddress = $IPAddress.RemoteAddress
}
Else {
$Value = $TESTConnection.PingSucceeded
$PingAddress = $IPAddress.RemoteAddress
$NICIP = "NOT REACHABLE"
}
# Create object
$Object = New-Object PSObject -Property ([ordered]#{
Computer_Target = $computer
ONLINE = $Value
IPAddress_PING = $PingAddress
IPResolved = $NIC2.IPAddress
NameResolved = $NIC.DNSHostName
MACAddress = $NIC.MACAddress
})
# Add object to array
$DataArray += $Object
#Display object
}
If($DataArray)
{
#Output Array Data into a CSV File
$DataArray | Export-Csv -Path $OutputFile -NoTypeInformation
}
#end of file
OK - I finally figured it out because I kept going around in circles and getting nowhere.
Thank you to Zett42 who led me in the right direction but I was still failing.
My first problem was that I hadn't identified $NIC2 as an Array which was part of my issue when trying $NIC2[0]. However, even after being Defined as an Array it still didn't work.
I then created another array and did this $NICIP = $NIC2.IPAddress and then output $NICIP[0] which works. Not exactly sure why that works.
Here is the entire code
#Definitions of variables
#************************
#Defines the location and name of the text file from where computers will be pulled
$Computers = Get-Content "c:\Temp\computers.txt"
#Defines the variable and format for Date
$Date = Get-date -UFormat %h_%d_%y_%H%M
#Define the name of the files to output
$OutputFile = "C:\Temp\Verify-Computer-$Date.csv" #All Computers On-Line Status will be provided in this file
#Defines the Array where to put the data and the $Ping variable to store the IP address
$DataArray = #()
$Resolution = New-Object System.Object.NetworkInformation.Ping
#Define A Function that will test which computers are on-line and create files to use in the other function.
ForEach ($computer in $Computers)
{
#Defining the variables
$TESTConnection = $null
$Object = $null
$Value = $null
$IPAddress = $null
$NameResolution = $null
$PingAddress = $null
$NIC = $null
$NIC2 = #()
$NICIP = #()
# Ping computer
$TESTConnection = Test-NetConnection -ComputerName $computer | Select-Object PingSucceeded
#If connection is live Get IP address
IF ($TESTConnection.PingSucceeded -eq "True") {
$IPAddress = Test-NetConnection -ComputerName $computer -InformationLevel "Detailed" | Select-Object RemoteAddress
$NIC = Get-WmiObject win32_NetworkAdapterConfiguration -ComputerName $computer | Where-Object {$_.DNSDomain -eq "contoso.com"} | Select-Object MACAddress, DNSHostName
$NIC2 = Get-WmiObject win32_NetworkAdapterConfiguration -ComputerName $computer | Where-Object {$_.DNSDomain -eq "contoso.com"} | Select-Object IPAddress
$Value = $TESTConnection.PingSucceeded
$PingAddress = $IPAddress.RemoteAddress
$NICIP = $NIC2.IPAddress
}
Else {
$Value = $TESTConnection.PingSucceeded
$PingAddress = "NOT REACHABLE"
}
# Create object
$Object = New-Object PSObject -Property ([ordered]#{
Computer_Target = $computer
ONLINE = $Value
IPAddress_PING = $PingAddress
IPResolved1 = $NICIP[0]
NameResolved = $NIC.DNSHostName
MACAddress = $NIC.MACAddress
})
# Add object to array
$DataArray += $Object
#Display object
}
If($DataArray)
{
#Output Array Data into a CSV File
$DataArray | Export-Csv -Path $OutputFile -NoTypeInformation
}
#end of file

powershell array results truncated for unknown reason

I've hacked together a script that lists all AD computer objects and enriches with some other information: Namely, is the machine pingable and what are the members of its local admin group. Each of these data elements are retrieved in an independent pipeline function.
For reasons unknown, the array of objects I'm pipelining is being truncated. Instead of the 883 computers returned from the GetAdComputer cmdlet, I'm left with only ~230 computers in the final CSV.
I've narrowed down the problem to the command that retrieves local admins via PS remoting. I'm sure there are machines for which this command fails so it's likely an exception is being thrown at some point. I assume such an exception may terminate the loop and lop off array items? I tried wrapping the Invoke-Command in a try/catch but that didn't resolve the issue. Still missing array items.
I'm sure this is a noob oversight. I don't use PS often enough to keep the language semantics straight. Any help is appreciated.
Import-Module active*
$now=(Get-Date).ToString("yyyyMMddTHHmmss")
Get-ADComputer -Filter 'Name -like "prod-idfi*"' -Properties LastLogonDate, Description |select LastLogonDate,DNSHostName,DistinguishedName,Description |foreach{
$alive = Test-Connection -CN $_.DNSHostName -Count 1 -BufferSize 16 -Quiet
return [PSCustomObject]#{
PSTypeName = "Computer"
Id=$null
Name = $_.DNSHostName
IsAlive = If($alive) {1} Else {0}
DistinguishedName = $_.DistinguishedName
Description = $_.Description
LastLogonDate = $_.LastLogonDate
LocalAdmins=''
}
}|foreach{
$computer = $_
if ($computer.IsAlive){
try{
$localAdmins = Invoke-Command -ComputerName $_.Name {([ADSI]"WinNT://./Administrators").psbase.Invoke('Members') | % { ([ADSI]$_).InvokeGet('AdsPath')}}
$computer.LocalAdmins= "$($localAdmins -join ",")"
}
catch {
Write-Host ("something bad happened")
}
}
$computer
} |Export-Csv ".\ComputerInventory_$now.csv" -NoTypeInformation
You do not need to pipe to a second ForEach-Object, since all can be done inside the first loop. Also, the Select-Object can be left out.
In order to enter the catch block also on non-terminating errors, you need to add parameter -ErrorAction Stop to the Invoke-Command.
Your code rewritten
Import-Module Active*
$now = (Get-Date).ToString("yyyyMMddTHHmmss")
$localGroup = 'Administrators'
Get-ADComputer -Filter "Name -like 'prod-idfi*'" -Properties LastLogonDate, Description |
ForEach-Object {
$alive = Test-Connection -ComputerName $_.DNSHostName -Count 1 -BufferSize 16 -Quiet
$localAdmins = if ($alive) {
try {
Invoke-Command -ScriptBlock {
([ADSI]"WinNT://./$localGroup").psbase.Invoke('Members') |
ForEach-Object { ([ADSI]$_).InvokeGet('AdsPath') -join ', '}
} -ErrorAction Stop
}
catch {
Write-Warning "Could not retrieve members of the local '$localGroup' group"
}
}
else { '' }
[PSCustomObject]#{
PSTypeName = "Computer"
Id = $null
Name = $_.DNSHostName
IsAlive = [int]$alive
DistinguishedName = $_.DistinguishedName
Description = $_.Description
LastLogonDate = $_.LastLogonDate
LocalAdmins = $localAdmins
}
} | Export-Csv -Path ".\ComputerInventory_$now.csv" -NoTypeInformation

How to run Powershell on a list of computers

I'm trying to run a remote command on a list of computers that available in plain text file (1 computer per line) in a file named 1.txt available under c:\1\1.txt.
What I run the powershell script the variable $comp is being run as $comp instead of being changed to the computer name
$computers = Get-Content c:\1\1.txt
foreach ($comp in $computers){
$LicenseInfo = Get-WmiObject SoftwareLicensingProduct -ComputerName $comp | Where-Object { $_.partialProductKey -and $_.ApplicationID -eq "55c92734-d682-4d71-983e-d6ec3f16059f" } | Select-Object PartialProductKey, Description, ProductKeyChannel, #{ N = "LicenseStatus"; E = { $lstat["$($_.LicenseStatus)"] } }
echo $LicenseInfo, $comp
}
run the powershell command with the Computername $comp - where $comp will be changed everytime in the loop for another name of a computer available in the c:\1\1.txt file
The reason you are getting the Select-Object error is because echo is not a command in Powershell that is for batch. Powershell uses Write-Host There is more information here:
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/write-host?view=powershell-4.0
As far as the code adding a delimiter switch to get-content will separate each computer name
Get-Content -Path c:\1\1.txt -Delimiter `r
The `r stands for a carriage return
Depending on how you want the output, you could try like this:
foreach ($comp in $computers) {
$LicenseInfo = Get-WmiObject SoftwareLicensingProduct -ComputerName $comp | Where-Object { $_.partialProductKey -and $_.ApplicationID -eq "55c92734-d682-4d71-983e-d6ec3f16059f" } | Select-Object PartialProductKey, Description, ProductKeyChannel, #{ N = "LicenseStatus"; E = { $lstat["$($_.LicenseStatus)"] } }
$LicenseInfo
$comp
}
The positional parameter error you're getting in your code is because you're giving echo variables and text, and the text isn't encapsulated in quotes.
You don't need echo or even Write-Host here if you just want to output the contents of your variables.

Selecting value from first pipelined argument

I wrote a PowerShell script that gets some cluster information. One of the columns I need is from the first argument in the pipeline and I can't find a way to return it's value.
function Get-SQL-Clusters {
Param([string]$server)
$servers = Get-Content -LiteralPath "C:\temp\sql_clusters.txt"
if ($server -ne 1) {
$files = foreach ($box in $servers) {
Invoke-Command -ComputerName $box {
Get-ClusterResource | Get-ClusterParameter
} | Where-Object {
$_.Name -eq "Address"
} | Format-Table PSComputerName, ClusterObject, State, Name, Value -AutoSize
}
} else {
Write-Warning "'$server' is not a valid path."
}
return $files
}
When I run this, I get the data I need but State is blank. It's in Get-ClusterResource, but the IP, which is what I'm mostly looking for, is in Get-ClusterParameter.
Ideally I would like to return the name of the cluster, each of the alwayson names, it's IP and it's current state so I can see if the active IP is on the primary site or on the DR site.
Your call to Invoke-Command places the Get-ClusterResource | Get-ClusterParameter calls into its own script block {...}, then pipes the results of evaluating those expressions to the Where-Object cmd. This may not be the intended order of operations.
Project your results using the Select-Object cmdlet at intermediate places in your pipeline to give you access to the desired properties at later stages (specific syntax hasn't been checked;YMMV):
Invoke-Command -ComputerName $box { Get-ClusterResource | Select-Object -Property State, #{Name="ClusterParameter";Expression = {(Get-ClusterParameter -InputObject $_) }}| Where-Object { $_.ClusterParameter.Name -eq ...
Will produce objects like:
State | ClusterParameter
------------------------
foo ClusterParameter.ToString()
The almost final code. It's not 100% complete but I get the State and IP values now and will fix the rest later. Another change I made was to stop using text files and created a hash table for my servers because of formatting problems I had with text files.
function Get-SQL-Clusters-scrap
{
param([string]$server)
import-module c:\temp\sql_hashtable2.ps1
$servers = $sql_servers.hadr
if ($server -ne 1)
{
$files = ForEach ($box in $servers) {invoke-command -ComputerName $box {Get-ClusterResource |
foreach-object {($state) = ($_.State); $_ |
get-clusterparameter |Where-Object {$_.Name -eq "Address"} |
Format-Table ClusterObject,#{Name=”State”;Expression={$state}}, Name, Value, PSComputerName -AutoSize}}}
}
else
{Write-Warning "'$server' is not a valid path."}
return $files
}

Writing all output to a file

I'm using the following code to get a list of machines with IP addresses. It prints out the hostname and IP address. If the host is offline, it says "$computername is offline." Here is the code:
$csv = Get-Content TEST_MACHINES.csv
foreach ($computer in $csv)
{
try
{
Test-Connection $computer -Count 1 -ErrorAction Stop | Select Address, IPV4Address
}
catch
{
"$computer is offline"
}
}
It works great and outputs the data like so:
Address IPV4Address
------- -----------
TESTMACHINE 192.168.1.1
TESTMACHINE2 192.168.1.2
TESTMACHINE3 is offline.
However no amount of trickery is allowing me to write all of this to a file, even though it's displaying like that in the console. It writes to a blank file or only writes the exception.
How can I capture this output exactly as it is?
You can create a custom powershell object using the same field names as the test-connection fields you are selecting and then export both success and failure to CSV. See below for an example:
$csv = Get-Content TEST_MACHINES.csv
foreach ($computer in $csv)
{
try
{
Test-Connection $computer -Count 1 -ErrorAction Stop | Select Address, IPV4Address | Export-Csv -Path .\ConnectionTest.csv -Append
}
catch
{
$Output = New-Object PSObject -Property #{
Address = $computer
IPV4Address = "Offline"
}
$Output | Export-Csv -Path .\ConnectionTest.csv -Append
}
}
In my style of writing scripts I'd use simple if..then..else loop. It seems most logical to me. You did try the "Out-File" switch after pipe, didn't you?... I have just run the below on localhost and some random name, and that worked just fine...
$csv = Get-Content TEST_MACHINES.csv
foreach ($computer in $csv)
{
if (Test-Connection $computer -Count 1 -Quiet)
{
Test-Connection $computer -Count 1 -ErrorAction Stop | Select Address, IPV4Address | Out-file -append "SomeFile.txt"
}
else
{
"$computer is offline" | Out-File -Append "SomeFile.txt"
}
}
Try this:
$csv = Get-Content TEST_MACHINES.csv
'' > foo.log
foreach ($computer in $csv)
{
try
{
Test-Connection $computer -Count 1 -ErrorAction Stop | Select Address, IPV4Address >> foo.log
}
catch
{
"$computer is offline" >> foo.log
}
}