Append to array in powershell job - powershell

I was able to find a piece of code that could ping all systems at once, better than any other job examples I've come across. This thing can take an entire file full of hosts, line by line, and ping them all literally at the same time. But how can I add the ones that are up to my $online array? I tried adding in the true block but it didn't work. Im simply trying to stick $online += $pc somewhere. Any help would be appreciated. Thanks.
$online = #()
$pc = Get-Content C:\servers.txt
$pc | 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

You can store the result of your jobs and then filter by Reachable. I've also simplified your code a bit and added -AutoRemove which I consider important to dispose your jobs when done.
$result = Get-Content C:\servers.txt | ForEach-Object {
Test-Connection -ComputerName $_ -Count 1 -AsJob
} | Receive-Job -Wait -AutoRemoveJob | ForEach-Object {
[pscustomobject]#{
ComputerName = $_.Address
Reachable = $_.StatusCode -eq 0
}
}
$online = $result | Where-Object Reachable
# if you want just the `ComputerName` values, you can do
$online = $result | Where-Object Reachable | ForEach-Object ComputerName
# or easier, using member-access enumeration and `.Where` method
$online = $result.Where{ $_.Reachable }.ComputerName
If you're interested in grouping the results between Reachable and Not Reachable during enumeration, the way to do it is with a hash table having 2 List<T> values.
$result = #{
Online = [System.Collections.Generic.List[object]]::new()
Offline = [System.Collections.Generic.List[object]]::new()
}
Get-Content C:\servers.txt | ForEach-Object {
Test-Connection -ComputerName $_ -Count 1 -AsJob
} | Receive-Job -Wait -AutoRemoveJob | ForEach-Object {
$obj = [pscustomobject]#{
ComputerName = $_.Address
Reachable = $_.StatusCode -eq 0
}
if($obj.Reachable) {
return $result['Online'].Add($obj)
}
$result['Offline'].Add($obj)
}
$result.Online.ComputerName # => has all reachable records

I believe the issue here is the pipe ft -autosize.
Try to pipe after the if/else statement as per below:
| ForEach-Object {
if ($_.Reachable -eq $true) {
$online += $_.ComputerName
}
}
Then if you want to view the results you can always do:
$online | ft -AutoSize
I'd also suggest a better formatting as all one line isn't easy to read. Try something like this:
$online = #()
$pc = Get-Content C:\servers.txt
$pc | 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
}
}} | ForEach-Object {
if ($_.Reachable -eq $true) {
$online += $_.ComputerName
}
}
$online | ft -AutoSize

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

powershell workflow and parallel not giving output

I'm using the following script and it creates the out file correctly, but it's empty. I'm not sure what it's missing exactly. This is my first attempt with using workflows.
workflow DisableIISParallel
{
$ScriptPath = "C:\Scripts\Server_Lists"
$OLD = "Legacy-Servers"
$OldList = Get-Content "$ScriptPath\$OLD.txt"
$objHost=$Records.Length
function DisableIIS ($appName) {
$objHostStr = [System.Net.Dns]::GetHostEntry([string]$objHost).HostName
Invoke-Command -ComputerName $objHostStr { iisreset /stop }
Set-Service -Name W3SVC -StartupType Disabled -Status Stopped -ComputerName $objHostStr
Get-WmiObject -Class win32_service -ComputerName $objHostStr |
where { $_.name -eq "W3SVC" } |
Format-Table -Property #{Expression={$_.PSComputerName};Label="Server";width=18},
#{Expression={$_.Name};Label="Service";width=45},
#{Expression={$_.StartMode};Label="Mode";width=10},
#{Expression={$_.State};Label="State";width=10},
#{Expression={$_.Status};Label="Status";width=10} |
Format-List | out-string -s | ? {$_} | Out-File C:\Scripts\Output\$appName.log -Append
Write-Output "" | Out-File C:\Scripts\Output\$appName.log -Append
}
foreach -parallel($objHost in $OldList)
{
$appName = $OLD
DisableIIS $appName
}
}
DisableIISParallel
This bit is not right:
Get-WmiObject -Class win32_service -ComputerName $objHostStr |
where { $_.name -eq "W3SVC" } |
Format-Table -Property #{Expression={$_.PSComputerName};Label="Server";width=18},
#{Expression={$_.Name};Label="Service";width=45},
#{Expression={$_.StartMode};Label="Mode";width=10},
#{Expression={$_.State};Label="State";width=10},
#{Expression={$_.Status};Label="Status";width=10} |
Format-List | out-string -s | ? {$_} | Out-File C:\Scripts\Output\$appName.log -Append
You should not run the output of one format command (Format-Table) into another format command (Format-List).
Try this (assuming you want tabular format - if not, change the Format-Table to Format-List):
Get-WmiObject -Class win32_service -ComputerName $objHostStr |
where { $_.name -eq "W3SVC" } |
Format-Table #{Expression={$_.PSComputerName};Label="Server";width=18},
#{Expression={$_.Name};Label="Service";width=45},
#{Expression={$_.StartMode};Label="Mode";width=10},
#{Expression={$_.State};Label="State";width=10},
#{Expression={$_.Status};Label="Status";width=10} >> C:\Scripts\Output\$appName.log
And pass in all the variables required to the function e.g.:
function DisableIIS ($appName, $objHost) {
...
}
foreach -parallel($objHost in $OldList)
{
$appName = $OLD
DisableIIS $appName $objHost
}

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