Writing all output to a file - powershell

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
}
}

Related

Selection of only one user from list

I have this script that I need to use to retrieve the data of a particular user "ADTuser" from a list of servers the script works well, but the output file with my user add also other users' detail that is not needed for my final output how can I filter it to only the user that I need.
get-content C:\servers.txt | foreach-object {
$Comp = $_
if (test-connection -computername $Comp -count 1 -quiet) {
([ADSI]"WinNT://$comp").Children | ?{$_.SchemaClassName -eq 'user' } | %{
$groups = $_.Groups() | %{$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
$_ | Select #{n='Computername';e={$comp}},
#{n='UserName';e={$_.Name}},
#{n='Memberof';e={$groups -join ';'}},
#{n='status'; e={if($groups -like "*Administrators*"){$true} else{$false}}}
}
} Else {Write-Warning "Server '$Comp' is Unreachable hence Could not fetch data"}
} | Out-File -FilePath C:\users.txt
This should be an easier way of doing what you're looking for, Get-CimInstance and Get-CimAssociatedInstance have been around since PowerShell 3:
Get-Content C:\servers.txt | ForEach-Object {
$computer = $_
try {
$query = Get-CimInstance Win32_UserAccount -Filter "Name='ADTuser'" -ComputerName $_ -ErrorAction Stop
foreach($object in $query) {
$membership = Get-CimAssociatedInstance -InputObject $object -ResultClassName Win32_Group -ComputerName $_
[pscustomobject]#{
Computername = $_
UserName = $object.Name
Memberof = $membership.Name -join ';'
Status = $membership.Name -contains 'Administrators'
}
}
}
catch {
Write-Warning "Server '$computer' is Unreachable hence Could not fetch data"
}
} | Export-Csv C:\users.csv -NoTypeInformation
If that doesn't work for you, your code would require a simple modification on your first filtering statement:
Where-Object { $_.SchemaClassName -eq 'user' -and $_.Name.Value -eq 'ADTuser' }
It's important to note that Test-Connection -ComputerName $_ -Count 1 -Quiet is not a relevant test for this script, this command is testing for ICMP response and adsi over WinNT requires RPC connectivity as well SMB.
Putting it all together with minor improvements the script would look like this:
Get-Content C:\servers.txt | ForEach-Object {
if (-not (Test-Connection -ComputerName $_ -Count 1 -Quiet)) {
Write-Warning "Server '$_' is Unreachable hence Could not fetch data"
return
}
$computer = $_
([adsi]"WinNT://$_").Children.ForEach{
if($_.SchemaClassName -ne 'user' -and $_.Name.Value -ne 'ADTuser') {
return
}
$groups = $_.Groups().ForEach([adsi]).Name
[pscustomobject]#{
Computername = $computer
UserName = $_.Name.Value
Memberof = $groups -join ';'
Status = $groups -contains 'Administrators'
}
}
} | Export-Csv C:\users.csv -NoTypeInformation

How to check if hotfix KBxxxxxx (example: KB4012212) is installed in the PC or not?

I will create (with PowerShell script) a table and add the result(Positive/negative) to it.
I have a text file computers.txt, in which all PCs are listed.
Like this
CSNAME Hotfixinfo
PC1 is installed
PC2 is not installed
PC3 is installed
etc.
With my actual script I can only see the positive result.
Get-Content .\computers.txt | Where {
$_ -and (Test-Connection $_ -Quiet)
} | foreach {
Get-Hotfix -Id KB4012212 -ComputerName $_ |
Select CSName,HotFixID |
ConvertTo-Csv |
Out-File "C:\$_.csv"
}
I'd suggest parsing through and handling the positive and negative results (also faster than the pipeline ForEach-Object):
:parse ForEach ($Computer in (Get-Content C:\Path\computers.txt))
{
If (Test-Connection $Computer -Quiet)
{
$Result = Get-Hotfix -Id KB4012212 -ComputerName $Computer -ErrorAction 'SilentlyContinue'
If ($Result)
{
$Result |
Select-Object -Property CSName,HotFixID |
ConvertTo-Csv -NoTypeInformation |
Out-File "C:\$Computer.csv"
Continue :parse
}
"`"CSName`",`"HotFixID`"`r`n`"$Computer`",`"NUL`"" |
Out-File "C:\$Computer.csv"
} Else { Write-Host 'Unable to connect to $Computer' }
}

PowerShell Mass Test-Connection

I am attempting to put together a simple script that will check the status of a very large list of servers. in this case we'll call it servers.txt. I know with Test-Connection the minimum amount of time you can specify on the -count switch is 1. my problem with this is if you ended up having 1000 machines in the script you could expect a 1000 second delay in returning the results. My Question: Is there a way to test a very large list of machines against test-connection in a speedy fashion, without waiting for each to fail one at a time?
current code:
Get-Content -path C:\Utilities\servers.txt | foreach-object {new-object psobject -property #{ComputerName=$_; Reachable=(test-connection -computername $_ -quiet -count 1)} } | ft -AutoSize
Test-Connection has a -AsJob switch which does what you want. To achieve the same thing with that you can try:
Get-Content -path C:\Utilities\servers.txt | ForEach-Object { Test-Connection -ComputerName $_ -Count 1 -AsJob } | Get-Job | Receive-Job -Wait | Select-Object #{Name='ComputerName';Expression={$_.Address}},#{Name='Reachable';Expression={if ($_.StatusCode -eq 0) { $true } else { $false }}} | ft -AutoSize
Hope that helps!
I have been using workflows for that. Using jobs spawned to many child processes to be usable (for me).
workflow Test-WFConnection {
param(
[string[]]$computers
)
foreach -parallel ($computer in $computers) {
Test-Connection -ComputerName $computer -Count 1 -ErrorAction SilentlyContinue
}
}
used as
Test-WFConnection -Computers "ip1", "ip2"
or alternatively, declare a [string[]]$computers = #(), fill it with your list and pass that to the function.
Powershell 7 and Foreach-Object -Parallel makes it much simpler now:
Get-Content -path C:\Utilities\servers.txt | ForEach-Object -Parallel {
Test-Connection $_ -Count 1 -TimeoutSeconds 1 -ErrorAction SilentlyContinue -ErrorVariable e
if ($e)
{
[PSCustomObject]#{ Destination = $_; Status = $e.Exception.Message }
}
} | Group-Object Destination | Select-Object Name, #{n = 'Status'; e = { $_.Group.Status } }

Table not showing output in new line

I'm sure there is a simple solution, but I'm stuck. The output in the members column is like this
{domain\Domain Admins, domain\joerod...
How can I show the
$member
value on each line?
Function Get-AdminGroups{
foreach($i in (Get-Content C:\Users\joerod\Desktop\remove_users.txt)){
#test if machine is on the network
if (-not (Test-Connection -computername $i -count 1 -Quiet -ErrorAction SilentlyContinue)) {
Write-Warning "$i is Unavalible"
"`r"
}
else {
(invoke-command {
$members = net localgroup administrators |
? {$_ -AND $_ -notmatch "command completed successfully"} |
select -skip 4
New-Object PSObject -Property #{
Computername = $env:COMPUTERNAME
Users=$members
}
} -computer $i -HideComputerName |
Select * -ExcludeProperty RunspaceID )
}
}
}
Get-AdminGroups |ft
Iterate through $members and make an object for each one. This creates an empty array, loops through the computers in your text file, and in that loop it pulls a list of the local administrators, and for each one it creates a custom object just like you are doing, and it adds it to that array.
$Results = #()
foreach($i in (GC C:\Users\joerod\Desktop\remove_users.txt)){
#test if machine is on the network
if (!(Test-Connection -computername $i -count 1 -Quiet -ErrorAction SilentlyContinue)) {
Write-Warning "$i is Unavalible`r"
Continue
}
invoke-command {
$members = net localgroup administrators |?{$_ -AND $_ -notmatch "command completed successfully"} | select -skip 4
ForEach($member in $members){
$Results += New-Object PSObject -Property #{
Computername = $env:COMPUTERNAME
Users=$member
}
}
} -computer $i -HideComputerName # | Select * -ExcludeProperty RunspaceID
}
$Results | FT

Powershell capturing loop output to a variable

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.