Speed up Test-Connection before Foreach - powershell

I made a script to check if users desktop folder are under the cuota limitation, if they're under the cuota limitation the backup to the server will be done correctly.
each user have his computer, so source CSV looks like:
pc1,user1
pc2,user2
pc800,user800
Some computers are Windows Xp and some W7, and the paths can be different 'cause of that I'm using Test-Path
W7 = C:\users\$user\desktop
XP = C:\document and settings\$user\desktop
But Test-Path is SUPER SLOW and I started to use a Test-Connection -count 1 before each Test-path
Anyway, the script still SLOW, in each "bad ping test" I lose lot of time.
CODE:
$csvLocation = '~\desktop\soourceReport.csv'
$csv = import-csv $csvLocation -Header PCName, User
$OuputReport = '~\desktop\newReport.csv'
# info:
# "209715200" Bytes = 200 MB
$cuota = "209715200"
$cuotaTranslate = "$($cuota / 1MB) MB"
Write-Host "Cuota is set to $cuotaTranslate"
$count=1
foreach($item in $csv)
{
write-host "$count# Revisando" $item.User "en" $item.PCName "..." #For debug
if (Test-Connection -Quiet -count 1 -computer $($item.PCname)){
$w7path = "\\$($item.PCname)\c$\users\$($item.User)\desktop"
#echo $w7path #debug
$xpPath = "\\$($item.PCname)\c$\Documents and Settings\$($item.User)\Escritorio"
#echo $xp #debug
if(Test-Path $W7path){
$desktopSize = (Get-ChildItem -Recurse -force $w7path | Measure-Object -ErrorAction "SilentlyContinue" -property length -sum)
write-host -ForegroundColor Green "access succeed"
if($($desktopSize.sum) -gt $cuota){
$newLine = "{0},{1},{2}" -f $($item.PCname),$($item.User),"$("{0:N0}" -f $($desktopSize.sum / 1MB)) MB"
$newLine | add-content $outputReport
Write-Host -ForegroundColor Yellow "cuota exceeded! -- added"
}
else{
Write-Host -ForegroundColor DarkYellow "cuota OK"
}
}
elseif(Test-Path $xpPath){
$desktopSize = (Get-ChildItem -Recurse -force $xpPath | Measure-Object -ErrorAction "SilentlyContinue" -property length -sum)
write-host -ForegroundColor Green "access succeed"
if($($desktopSize.sum) -gt $cuota){
$newLine = "{0},{1},{2}" -f $($item.PCname),$($item.User),"$("{0:N0}" -f $($desktopSize.sum / 1MB)) MB"
$newLine | add-content $outputReport
Write-Host -ForegroundColor Yellow "cuota exceeded! -- added"
}
else{
Write-Host -ForegroundColor DarkYellow "cuota OK"
}
else{
write-host -ForegroundColor Red "Error! - bad path"
}
}
else{
write-host -ForegroundColor Red "Error! - no ping"
}
$count++
}
Write-Host -ForegroundColor green -BackgroundColor DarkGray "All done! new report stored in $report"
To improve it I stored all computers in a $list using another Foreach, before the firstly mentioned SLOW-Foreach loop.
foreach($pcs in $csv){
$alivelist += #( $pcs.PCName )
}
Test-Connection -quiet -count 2 -computer $alivelist
Now, I don't now how to UPDATE or remove the rows ("dead" pc,user) from the SOURCE CSV before to enter into the second Foreach.
I need some of your "magic", or at least some ideas!
thanks

To speed up your script you need to run the checks in parallel (as others have already mentioned). Put your checks and the worker code in a scriptblock:
$sb = {
Param($computer, $username)
if (Test-Connection -Quiet -Count 2 $computer) { return }
$w7path = "\\$computer\c$\users\$username\desktop"
$xpPath = "\\$computer\c$\Documents and Settings\$username.TUITRA..."
if (Test-Path $W7path) {
#...
} elseif (Test-Path $xpPath) {
#...
} else {
#...
}
}
Then run the scriptblock as parallel jobs:
$csv | ForEach-Object {
Start-Job -ScriptBlock $sb -ArgumentList $_.PCName, $_.User
}
# wait for completion
do {
Start-Sleep -Milliseconds 100
} while (Get-Job -State 'Running')
# cleanup
Get-Job | ForEach-Object {
Receive-Job -Id $_.Id
Remove-Job -Id $_.Id
} | Out-File $outputReport
Use a queue if you need to limit the number of parallel jobs.

test-connection is weirdly fast with the -asjob parameter, pinging about 200 computers in 4 seconds:
$list = cat hp.txt
test-connection $list -AsJob ; job | receive-job -wait -AutoRemoveJob

Related

Why is my if statement not being read properly?

I have a script that backs up a user profile from the local, or remote machine and places it onto a share: $Global:Shared_BackupPath = "\\server\share\". I've been tweaking it a little more and just ended up making some variables into Global variables (not sure if this is the issue - dont see why it would be).
This is the condition:
if(-not (Get-EventSubscriber)){
I tried changing it to -eq $null to see if it would make any difference, but it didn't.
Its just not analyzing the condition properly and goes on to display my message box before all Jobs are done: it's "supposed" to wait till there's no more events and then display the message box:
Register-ObjectEvent -InputObject $job -EventName StateChanged -Action {
#Start-Sleep -Milliseconds 500
$eventSubscriber | Unregister-Event
$eventSubscriber.Action | Remove-Job
if(-not (Get-EventSubscriber)){
$Profile_Sum = Get-ChildItem -Path $Global:BackUp_Path -Recurse |
Measure-Object -Property length -Sum |
Select-Object -ExpandProperty Sum
$Size = try{if($Profile_Sum -lt 1048576){ $TinyByte = " {0:N2}" -f ($Profile_Sum / 1KB) + " KB"; $TinyByte }
elseif($Profile_Sum -gt 1048576 -and $Profile_Sum -lt 1073741824){ $MediumByte = " {0:N2}" -f ($Profile_Sum / 1MB) + " MB"; $MediumByte }
elseif($Profile_Sum -gt 1073741824){ $GiganticByte = " {0:N2}" -f ($Profile_Sum / 1GB) + " GB"; $GiganticByte } } Catch {}
$Begin_Time = Get-Item -Path $Global:BackUp_Path | Select-Object -ExpandProperty LastWriteTime
$End_Time = Get-Date -Format G
Get-Job | Remove-Job
[System.Windows.MessageBox]::Show("Copying Complete!`nStart Time: $Begin_Time `nEnd Time: $End_Time `nProfile Size copied: $Size")
}
} | Out-Null
}
I feel like I may have an idea due to the event itself being registered as a job but, not too sure how to go about it and have it wait until its done with ALL jobs before displaying the messagebox informing me when the copying is complete. Other than that, the script works just fine and anyone reading this can feel free to use it themselves. Heres the full Script:
Function PFL-UserBackUp{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$false,
ValueFromPipeLine=$true,
ValueFromPipeLineByPropertyName=$true)]
[Alias('CN','Computer','server','node')]
[ValidateLength(1, 15)]
[String[]]$ComputerName = $env:COMPUTERNAME )
Begin{
$Global:Shared_BackupPath = "\\server\share\"
}
Process{
Foreach($Computer in $ComputerName){
Try{
$PSSession = New-PSSession -ComputerName $Computer -ErrorAction Stop
[array]$User_List = Invoke-Command -ScriptBlock {
Get-ChildItem -Path "C:\Users" -Exclude Public, Default* |
Sort-Object -Property LastWriteTime -Descending } -Session $PSSession
$userinfo1 = foreach ($user in $User_List.name) {
$userinfo = (net user $user /domain | Select-String "Full Name" -ErrorAction SilentlyContinue) -replace "Full Name ", "" 2>&1 | Out-String -Stream
if ($userinfo.Length -lt 4) { "NO DISPLAY NAME in ADUC" }
elseif($LASTEXITCODE -eq 2) { "ACCOUNT NOT in ADUC" }
elseif($LASTEXITCODE -eq 0) { $userinfo }
else { "Error occured" }
}
$(for($i=0; $i -lt $User_List.Count; $i++){
[pscustomobject]#{
'User Display Name ' = "$($i): $($userinfo1[$i])"
' NAME ' = $User_List.name[$i]
'Last Modified' = "$($User_List.LastWriteTime[$i])"
'Profile Size ' = Try{
$ProfilePath = $User_List.FullName[$i]
$Profile_Sum = Invoke-Command -ScriptBlock {
Get-ChildItem -Path $Using:ProfilePath -Recurse |
Where-Object {$_.PSParentPath -match "Documents|Desktop|Music|Videos|Downloads|Links|Pictures|Favorites|Contacts" -and $_.DirectoryName -notmatch "OneDrive" } |
Measure-Object -Property length -Sum |
Select-Object -ExpandProperty Sum } -Session $PSSession
if($Profile_Sum -lt 1048576){ $TinyByte = " {0:N2}" -f ($Profile_Sum / 1KB) + " KB"; $TinyByte }
elseif($Profile_Sum -gt 1048576 -and $Profile_Sum -lt 1073741824){ $MediumByte = " {0:N2}" -f ($Profile_Sum / 1MB) + " MB"; $MediumByte }
elseif($Profile_Sum -gt 1073741824){ $GiganticByte = " {0:N2}" -f ($Profile_Sum / 1GB) + " GB"; $GiganticByte } #Profile Size
} Catch { "$($Error[0].Exception.Message.Split('.')[2].Trim())!" }
}
} ) | Out-Host
Write-Host "Press 'Q' to quit."
$ii = Read-Host -Prompt "Enter Number of Profile to Back-Up"
$i = $ii.Trim() -split ","
if([String]::IsNullOrEmpty($i) -eq $true) { "Null string"; Break }
elseif($i.ToLower() -like "q*") {"Q was selected. Stopping script."; Break }
<#
" "
" Following Profiles will be Saved:"
" ------------------------------------"
foreach($i in $index) { "$($i.trim()): $($userinfo1[$i])" }
" "
$Confirm = Read-Host -Prompt "Are you sure you want to continue? [Y/N]"
if($Confirm.ToLower().TrimStart() -like "n*" -or $Confirm.ToLower() -like "q*"){Break}
if([String]::IsNullOrEmpty($Confirm.Trim()) -eq $true) { "Null string"; Break }#>
$Profile_Path = "C:\Users\$($User_List.name[$i])"
$Literal_Name = $userinfo1[$i].Replace('/','-')
$Global:BackUp_Path = "$Global:Shared_BackupPath$Literal_Name"
$Test_Path = Test-Path -Path $Global:BackUp_Path
if($Test_Path -eq $false){
New-Item -Path $Global:BackUp_Path -ItemType Directory | Out-Null
Start-Process $Global:BackUp_Path}
elseif($Test_Path -eq $true){
$Find_OldName = Get-ChildItem -Path "$Global:Shared_BackupPath" -Filter "$Literal_Name" -Directory |
Sort-Object -Property LastWriteTime -Descending |
Select-Object -ExpandProperty BaseName -First 1
$New_PathName = $Find_OldName + "1"
New-Item -Path "$Global:Shared_BackupPath" -Name $New_PathName -ItemType Directory -OutVariable Global:BackUp_Path | Out-Null #Global:BackUp_Path variable declared
$Global:BackUp_Path = $Global:BackUp_Path.FullName
Start-Process $Global:BackUp_Path}
$Global:Start_Time = Get-Date -Format G
#Favorites Copy
$FireFox_Favorites = "C:\Users\$($User_List.name[$i])\AppData\Roaming\Mozilla\Firefox\Profiles\*.default\places.sqlite"
$Chrome_Favorites = "C:\Users\$($User_List.name[$i])\AppData\Local\Google\Chrome\User Data\Default\Bookmarks"
$Chrome_Favorites2 = "C:\Users\$($User_List.name[$i])\AppData\Local\Google\Chrome\User Data\Default\Bookmarks.bak"
$Sticky_Notes = "C:\Users\$($User_List.name[$i])\AppData\Local\Packages\Microsoft.MicrosoftStickyNotes_8wekyb3d8bbwe\LocalState\plum.sqlite"
$Favorites_Array = #($FireFox_Favorites,$Chrome_Favorites,$Chrome_Favorites2,$Sticky_Notes)
Foreach($File in $Favorites_Array){
$Test_File = Invoke-Command -ScriptBlock { Test-Path -Path $File }
if($Test_File -eq $true){
Copy-Item -Path $File -Destination $Global:BackUp_Path -Force -Recurse -FromSession $PSSession
}
}
#Folders Copy
$Folders = #('Desktop','Documents','Favorites','Links','Downloads','Music','Videos','Pictures','Contacts')
Foreach($Folder in $Folders){
#Create Arugments for seperate thread
$ArgumentsArray = $null
$ArgumentsArray = #()
$ArgumentsArray += "\\$Computer\c$\Users\$($User_List.name[$i])\$Folder"
$ArgumentsArray += $Global:BackUp_Path
$job = Start-Job -ScriptBlock { Copy-Item -Path $args[0] -Destination $args[1] -Force -Recurse } -Name $Folder -ArgumentList $ArgumentsArray
Register-ObjectEvent -InputObject $job -EventName StateChanged -Action {
#Start-Sleep -Milliseconds 500
$eventSubscriber | Unregister-Event
$eventSubscriber.Action | Remove-Job
if(-not (Get-EventSubscriber)){
$Profile_Sum = Get-ChildItem -Path $Global:BackUp_Path -Recurse |
Measure-Object -Property length -Sum |
Select-Object -ExpandProperty Sum
$Size = try{if($Profile_Sum -lt 1048576){ $TinyByte = " {0:N2}" -f ($Profile_Sum / 1KB) + " KB"; $TinyByte }
elseif($Profile_Sum -gt 1048576 -and $Profile_Sum -lt 1073741824){ $MediumByte = " {0:N2}" -f ($Profile_Sum / 1MB) + " MB"; $MediumByte }
elseif($Profile_Sum -gt 1073741824){ $GiganticByte = " {0:N2}" -f ($Profile_Sum / 1GB) + " GB"; $GiganticByte } } Catch {}
$Begin_Time = Get-Item -Path $Global:BackUp_Path | Select-Object -ExpandProperty LastWriteTime
$End_Time = Get-Date -Format G
Get-Job | Remove-Job
[System.Windows.MessageBox]::Show("Copying Complete!`nStart Time: $Begin_Time `nEnd Time: $End_Time `nProfile Size copied: $Size")
}
} | Out-Null
}
" "
Write-Output -InputObject "Copying will be done in background."
Write-Output -InputObject "You will be notified when copying is done."
} catch [System.Management.Automation.Remoting.PSRemotingTransportException]{
"Unable to connect to PC: $Computer `nError: $($Error[0].Exception.Message.Split('.')[2].Trim())!"
}
}
}
}
I removed some information that could get me in trouble but, it's all cosmetics.(:
EDIT: I must be on crack but, everything is working now. Only changes I made was clearing the global variables ($Global:var = $null) before assigning it a value. Thank you all for the suggestions. Another change i made was change Copy-Item to Robocopy instead.
To just answer your question:
Why is my if statement not being read properly?
Just before you evaluate Get-EventSubscriber your are cancelling your event subscription and thus deleting your event subscriber. Get-EventSubscriber therefore returns $null which evaluates to $true, when negated. In conclusion, the code after your if statement will always be executed immediately.
Commented code:
Register-ObjectEvent -InputObject $job -EventName StateChanged -Action {
$eventSubscriber | Unregister-Event # Here you cancel your event subscription
$eventSubscriber.Action | Remove-Job
if (-not (Get-EventSubscriber)) { # Therefore, Get-EventSubscriber returns $null; not $null evaluates to $true
...
The relevant part in the documentation of Unregister-Event:
The Unregister-Event cmdlet cancels an event subscription that was created by using the Register-EngineEvent, Register-ObjectEvent, or Register-WmiEvent cmdlet.
When an event subscription is canceled, the event subscriber is deleted from the session and the subscribed events are no longer added to the event queue. When you cancel a subscription to an event created by using the New-Event cmdlet, the new event is also deleted from the session.

Subexpression printing out same strings? Powershell

I have this code which deletes User Profiles off a remote machine. The removal of profiles work just fine but, the Aesthetic of doing so doesn't. What do i mean?
I'm passing the user display names to an index and making a selection out of it, and that works fine in regards to assigning the proper names to the appropriate Index Number its associated to in C:\users.
The next line of code is it grabbing the selections i made, and running through them displaying the same name i did for the index, and then it goes off to delete the CIM instance.
So my question is, why is it not passing the subexpression $userinfo1 that is already made and not putting it into the next block of code, for example, the following works as in grabbing the proper Display Name and assigning it to the proper Number:
$menu = (get-childitem "\\$cn\c$\users" | sort LastWriteTime -Descending).Name
$userinfo1 = foreach ($user in $menu) {
Start-Sleep -Milliseconds 2
$userinfo = (net user $user /domain | Select-String "Full Name" -ErrorAction SilentlyContinue) -replace "Full Name ", "" 2>&1 | Out-String -Stream
if ($userinfo.Length -lt 4) {
"$user - NO DISPLAY NAME in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 2) {
"$user - account not in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 0){
$userinfo # output
}
}
}
}
Write-Warning "Ensure user profiles are no longer active and/or, have profiles be backed-up!"
Write-Host "RESULTS:" -BackgroundColor Black -ForegroundColor White
for ($i=0; $i -lt $userinfo1.Count; $i++) {
Write-Host "$($i): $($userinfo1[$i])"
} #END LIST OF POSSIBLE NAMES
Write-Host ""
Write-Host "For multiple users, seperate using a SPACE(1 2 3)"
$selection = Read-Host "ENTER THE NUMBER of the user(s) or Q to quit"
$selection = $selection -split " "
but, the next block doesn't associate the display name (that was captured in $userinfo1) with the number i select and it just continues to display the first display name with the rest of the profiles its reiterating through:
foreach($Profile in $menu[$selection]){
Write-Host "Deleting user: $(,$userinfo1[$selection]) `
ID:$Profile "}
Hopefully this makes sense, and if anyone can point me in the right direction id greatly appreciate it!
Heres the rest of the script, please feel free to use it as it does work for deleting the actual profile off the system and not just the files.
#Deletes a profile properly off remote machine. WARNING: DOES NOT BACK UP DATA! Use at your own peril. Delprofile
$cn = Read-Host -Prompt "Enter Computer Name"
$ping = Test-Connection -ComputerName $cn -Count 1 -Quiet
If($ping -eq $false){ Write-Host "Computer seems to be offline, please check name spelling." -ForegroundColor DarkYellow; Write-Host ""; &PFL-Delete } else {
$menu = (get-childitem "\\$cn\c$\users" | sort LastWriteTime -Descending).Name
$userinfo1 = foreach ($user in $menu) {
Start-Sleep -Milliseconds 2
$userinfo = (net user $user /domain | Select-String "Full Name" -ErrorAction SilentlyContinue) -replace "Full Name ", "" 2>&1 | Out-String -Stream
if ($userinfo.Length -lt 4) {
"$user - NO DISPLAY NAME in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 2) {
"$user - account not in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 0){
$userinfo # output
}
}
}
}
Write-Warning "Ensure user profiles are no longer active and/or, have profiles be backed-up!"
Write-Host "RESULTS:" -BackgroundColor Black -ForegroundColor White
for ($i=0; $i -lt $userinfo1.Count; $i++) {
Write-Host "$($i): $($userinfo1[$i])"
} #END LIST OF POSSIBLE NAMES
Write-Host ""
Write-Host "For multiple users, seperate using a SPACE(1 2 3)"
$selection = Read-Host "ENTER THE NUMBER of the user(s) or Q to quit"
$selection = $selection -split " "
foreach($Profile in $menu[$selection]){
Write-Host "Deleting user: $(,$userinfo1[$selection]) `
ID:$Profile "
$del = Get-CimInstance -ComputerName $cn -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $Profile }
If($del -eq $null){Write-Warning "No CIM instance found on system, profile has been deleted but files persist. Delete manually!"} else{
Get-CimInstance -ComputerName $cn -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $Profile } | Remove-CimInstance -WhatIf
Write-Host "user profile has been deleted" -ForegroundColor Red
Write-Host ""}
}
}
#CountPs $cn
12/31/2020 - EDIT:
Here is the finished result:
Function Delete-PFL{
#Deletes a profile properly off remote machine. WARNING: DOES NOT BACK UP DATA! Use at your own peril. Delprofile
$cn = Read-Host -Prompt "Enter Computer Name"
$ping = Test-Connection -ComputerName $cn -Count 1 -Quiet
If($ping -eq $false){ Write-Host "Computer seems to be offline, please check name spelling." -ForegroundColor DarkYellow; Write-Host ""; &Delete-PFL } else {
$menu = (get-childitem "\\$cn\c$\users" | sort LastWriteTime -Descending).Name
$userinfo1 = foreach ($user in $menu) {
Start-Sleep -Milliseconds 2
$userinfo = (net user $user /domain | Select-String "Full Name" -ErrorAction SilentlyContinue) -replace "Full Name ", "" 2>&1 | Out-String -Stream
if ($userinfo.Length -lt 4) {
"$user - NO DISPLAY NAME in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 2) {
"$user - ACCOUNT NOT in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 0){
$userinfo # output
}
}
}
}
Write-Warning "Ensure user profiles are no longer active and/or, have profiles be backed-up!"
Write-Host "RESULTS:" -BackgroundColor Black -ForegroundColor White
for ($i=0; $i -lt $userinfo1.Count; $i++) {
Write-Host "$($i): $($userinfo1[$i])"
} #END LIST OF POSSIBLE NAMES
Write-Host ""
Write-Host "For multiple users, seperate using a SPACE(1 2 3)"
$selection = Read-Host "ENTER THE NUMBER of the user(s) or Q to quit"
$selection = $selection -split " "
foreach($index in $selection) {
$Profile = $menu[$index]
Write-Host "Deleting user: $($userinfo1[$index]) `
ID:$Profile "
$del = Get-CimInstance -ComputerName $cn -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $Profile }
If($del -eq $null){Write-Warning "No CIM instance found on system, profile has been deleted but files persist."
Write-Host "Attempting to delete files, please wait. . ."
Remove-Item -Path "\\$cn\c$\users\$Profile" -Force -WhatIf
Write-Host ""
Start-Sleep -Seconds 2
Write-Host "Checking if Files are still there. . ."
$TestPath = Test-Path -Path "\\$cn\c$\users\$Profile"
If($TestPath -eq $false){ Write-Host "Profile Files have been deleted. `
Continuing. . . ." -ForegroundColor Green
}
} else{
Get-CimInstance -ComputerName $cn -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $Profile } | Remove-CimInstance -WhatIf
Write-Host "user profile has been deleted" -ForegroundColor Red
Write-Host ""
}
}
}
#CountPs $cn
}
Remember to remove the -whatif parameter. Enjoy!
$selection is an array of indices, so in your foreach loop you must refer to the single index at hand, not to $selection as a whole, to get the desired display output.
The conceptually clearest approach is probably to iterate over the indices contained in $selection:
foreach($index in $selection) {
$Profile = $menu[$index]
Write-Host "Deleting user: $($userinfo1[$index]) `
EDIPI:$Profile "
# ...
}

Check if computer is online, if so, echo "$Computername is online" script [duplicate]

I have a large list of hostnames I need to ping to see if they are up or down. I'm not really that great at scripting but I managed to figure this much out:
$names = Get-content "hnames.txt"
foreach ($name in $names){
if (Test-Connection -ComputerName $name -Count 1 -ErrorAction SilentlyContinue){
Write-Host "$name is up" -ForegroundColor Green
}
else{
Write-Host "$name is down" -ForegroundColor Red
}
}
This gets me what I need but i now need to write out these results to a csv file and i have no idea how to do that.
Please Help!
You can use the following code instead (I simply altered the write-host calls to CSV formatting) and execute it with "PowerShell.exe script.ps > output.csv"
Note that you must execute it from the folder that contains hnames.txt, or simply change the "hnames.txt" to a full path.
$names = Get-content "hnames.txt"
foreach ($name in $names){
if (Test-Connection -ComputerName $name -Count 1 -ErrorAction SilentlyContinue){
Write-Host "$name,up"
}
else{
Write-Host "$name,down"
}
}
P.S. You can also use the Out-File Cmdlet to create the CSV file
I am a complete newbie to Powershell, so I took this on as a learning task, as I needed a quick and simple way to check a list of PC's for up/down status. These tweaks were needed to get it to output cleanly to the screen and to a txt file
$Output= #()
$names = Get-content "hnames.txt"
foreach ($name in $names){
if (Test-Connection -ComputerName $name -Count 1 -ErrorAction SilentlyContinue){
$Output+= "$name,up"
Write-Host "$Name,up"
}
else{
$Output+= "$name,down"
Write-Host "$Name,down"
}
}
$Output | Out-file "C:\support\result.csv"
$Output= #()
$names = Get-Content ".\input\Servers.txt"
foreach ($name in $names){
if (Test-Connection -Delay 15 -ComputerName $name -Count 1 -ErrorAction SilentlyContinue -quiet){
$Output+= "$name,up"
Write-Host "$Name,up" -ForegroundColor Green
}
else{
$Output+= "$name,down"
Write-Host "$Name,down" -ForegroundColor Red
}
}
$Output | Out-file ".\output\result.csv"
This is a tad cleaner, and includes the original foreground options but, BTW, the 'delay' switch seems to be ignored -PB
I would do it this way. Using a list of computers and -asjob works very well. The Responsetime property (confusingly the header is "Time(ms)") will be non-null if the host is up.
$names = Get-content hnames.txt
test-connection $names -asjob -count 1 | receive-job -wait -auto
Source Destination IPV4Address IPV6Address Bytes Time(ms)
------ ----------- ----------- ----------- ----- --------
COMP001 yahoo.com 74.6.231.21 32 39
COMP001 microsoft.com 40.113.200.201 32
Lately I do it this way. It requires threadjobs installed in powershell 5.1. Or just use get-port. I stick it in a mymod\mymod.psm1 module file somewhere in $env:psmodulepath. I can check a classroom in under 10 seconds.
function get-pport { # multi-threaded
param($list)
$list |
% { $_ | start-threadjob { get-port $input } -throttlelimit 20 } |
receive-job -wait -auto
}
function Get-Port {
Param (
[parameter(ValueFromPipeline)]
[string[]]$Hostname='yahoo.com'
)
begin {
$ports = 22,5988,3389,5985
$ping = New-Object System.Net.Networkinformation.ping
$Timeout = 200 # ms
}
process {
$hostname | foreach {
$openPorts = #()
foreach ($port in $ports) {
$client = New-Object System.Net.Sockets.TcpClient
$beginConnect = $client.BeginConnect($_,$port,$null,$null)
Start-Sleep -Milli $TimeOut
if($client.Connected) { $openPorts += $port }
$client.Close()
}
$result = $Ping.Send($_, $timeout)
if (! $result) { write-error "hostname $_ not found" }
$pingstatus = ($result.status -eq 'Success')
New-Object -typename PSObject -Property #{
HostName = $_
Port = $openPorts
Ping = $pingstatus
} | select hostname,port,ping
} # end foreach
} # end process
}
Example:
$avid = cat afile.txt
pport $avid
HostName Port Ping
-------- ---- ----
A006 {3389, 5985} True
A011 {3389, 5985} True
A015 {3389} True

Disk cleanup using PowerShell

I am trying to write a script for cleaning disks. All works except running cleanmgr. Can someone advise?
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.Namespace(0xA)
$computername = Read-Host -Prompt 'Enter machine name'
foreach ($hosts in $computername) {
#$temp = get-ChildItem "env:\TEMP"
#$temp2 = $emp.Value
#$swtools = "c:\SWTOOLS\*"
$ccmcache = "\\$hosts\c$\Windows\ccmcache\*"
$temp = "\\$hosts\C$\Users\*****\AppData\Local\Temp\*"
#$WinTemp = "c:\Windows\Temp\*"
$FreespaceBefore = (Get-WmiObject -Class win32_logicaldisk -ComputerName $hosts -Filter "DeviceID='C:'" | select Freespace).FreeSpace/1GB
#Write-Output "Disk Space Before"
Write-Output "$FreespaceBefore"
Write-Host "Removing Junk files in $ccmcache on $hosts." -ForegroundColor Green
Remove-Item -Recurse $ccmcache -Force -Verbose
Write-Host "Removing Junk files in $temp on $hosts." -ForegroundColor DarkYellow
Remove-Item -Recurse $temp -Force -Verbose
Write-Host "Finally now , Running Windows disk Clean up Tool" -ForegroundColor Cyan
Invoke-Command -ComputerName pc -ScriptBlock {Start-Process cleanmgr.exe}
#Write-Output "Disk Space after"
#Write-Output "$diskspaceafter"
$([char]7)
Sleep 1
$([char]7)
Sleep 1
Write-Host "I finished the cleanup task,Succefully " -ForegroundColor Yellow
}
2# Running Disk Clean up Tool
write-Host "Finally now , Running Windows disk Clean up Tool" -ForegroundColor Cyan
cleanmgr /sagerun:1 /VERYLOWDISK | out-Null
$([char]7)
Sleep 1
$([char]7)
Sleep 1
I know this question is old but for anyone still looking...
This link could provide a couple options, like the one below
foreach ($computer in $computers){
if(!(Test-Connection -Cn $computer -BufferSize 16 -Count 1 -ea 0 -quiet))
{write-host "cannot reach $computer" -f red}
else {& \\$computer\C$\Windows\System32\cleanmgr.exe /sagerun:1}}

Is there any Faster method to do WMI query from Powershell ..?

Wrote a small script to find the number Multipaths from the windows servers using WMI query. It works well for the servers which can connect directly without any issue. But if one server is pingable but not able to reach through WMI script, it takes long time to return the error ( for example if a linux server hostname is present in the servers.txt list).. Can somebody help me to do the same in a faster way..?
$Servers = Get-Content .\Servers.txt
$ErrorActionPreference = ‘SilentlyContinue’
FOREACH ($Server in $Servers) {
Write-Host $Server -nonewline
if (test-connection -computername $Server -Count 1 -quiet) {
$Name = $null
$NoPath =$null
$MPIODisks =$null
$MPIODisks = Get-WmiObject -Namespace root\wmi -Class mpio_disk_info -ComputerName "$Server" |Select-Object "DriveInfo"
if ($MPIODisks -eq $Null) {
write-host "`t - Unable to connect" -fore "RED"
} else {
write-host ""
write-host "Drive Name `tNo.Path" -fore "yellow"
Foreach ($Disk in $MPIODisks) {
$mpiodrives = $disk.DriveInfo
foreach ($Drive in $mpiodrives) {
$Name = $Drive.Name
$NoPath = $Drive.Numberpaths
If ($NoPath -lt 4) {
Write-Host $Name `t -nonewline
write-host $NoPath -fore "Red"
} else {
Write-Host $Name `t -nonewline
write-host $NoPath -fore "Green"
}
}
}
}
write-host ""
} else {
write-host "`t- Unknown Host" -fore "Red"
write-host ""
}
}
There is a connect item for Get-WmiObject to add a timeout parameter. A workaround noted in that item is to just pipe your WMI command to Wait-Job and specify a timeout period in seconds.
As long as your on PS version 3.0 or higher, this should work for you:
Get-WmiObject win32_computersystem -ComputerName <hostname> -AsJob | Wait-Job -Timeout 10 | Receive-Job
As an alternative, you could ask all servers for the result at once by passing them all into the query and avoiding the slow loop querying one server at a time. I don't have any MPIO drives to test with, but it could look something like this (using Get-Ciminstance which takes a timeout parameter):
$servers = Get-Content .\Servers.txt
# Get data from all servers with timeout
$servers_ok = Get-CimInstance -computername $servers -Namespace root\wmi -Class mpio_disk_info -ErrorAction SilentlyContinue -OperationTimeoutSec 1 | group pscomputername
# Output which servers gave no result back
foreach($no_result in $($servers | where { $_ -NotIn $servers_ok.Name })) {
write-host "No result for $no_result" -ForegroundColor Red
}
# Loop over the results and output
foreach($server in $servers_ok) {
Write-Host $server.Name
foreach($mpiodisk in $server.group)  {
$mpiodrives = $mpiodisk.DriveInfo
foreach ($mpiodrive in $mpiodrives) {
$name = $mpiodrive.Name
$noPath = $mpiodrive.NumberPaths
If ($NoPath -lt 4) {
write-host $name `t -nonewline
write-host $noPath -fore "Red"
} else {
write-host $name `t -nonewline
write-host $noPath -fore "Green"
}
}
}
}