Hi I have an issue in powershell where the Do Until Condition is true, but the loop doesn't stop. If I change the -eq to 0. It will stop... Basically what this should do is get the number of computers in the text file. Store that number in $count. Then restart the service for each computer in the list until it reaches the last one.
$computers = gc C:\temp\computers.txt
$count = $computers.count
Do {
foreach($computer in $computers){
$readCount = $computer.ReadCount
gwmi win32_service -ComputerName $computer | where {$_.name -like "*was*"} | Restart-Service
}
}
Until (($count - $readCount) -eq 1)
You don't need a Do-Until loop here since you can just iterate over the computers. To skip the last computer, use the Select-Object cmdlet with the -SkipLast 1 parameter:
Get-Content 'C:\temp\computers.txt' | Select-Object -SkipLast 1 | Forach-Object {
gwmi win32_service -ComputerName $computer |
where {$_.name -like "*was*"} |
Restart-Service
}
Related
The majority of this code was pulled from a blog online, but I think it's exactly the way I need to be tackling this. I want to get the top 4 machines from an OU based on uptime, and run a script that lives on each of the top 4 machines. I know that the problem involves the Array losing access to the Get-ADComputer properties, but I'm unsure of how to pass these new properties back to their original objects. This works as expected until it gets to the foreach loop at the end.
$scriptBlock={
$wmi = Get-WmiObject -Class Win32_OperatingSystem
($wmi.ConvertToDateTime($wmi.LocalDateTime) – $wmi.ConvertToDateTime($wmi.LastBootUpTime)).TotalHours
}
$UpTime = #()
Get-ADComputer -Filter 'ObjectClass -eq "Computer"' -SearchBase "OU=***,OU=***,OU=***,DC=***,DC=***" -SearchScope Subtree `
| ForEach-Object { $Uptime += `
(New-Object psobject -Property #{
"ComputerName" = $_.DNSHostName
"UpTimeHours" = (Invoke-Command -ComputerName $_.DNSHostName -ScriptBlock $scriptBlock)
}
)
}
$UpTime | Where-Object {$_.UpTimeHours -ne ""} | sort-object -property #{Expression="UpTimeHours";Descending=$true} | `
Select-Object -Property ComputerName,#{Name="UpTimeHours"; Expression = {$_.UpTimeHours.ToString("#.##")}} | Select-Object -First 4 |`
Format-Table -AutoSize -OutVariable $Top4.ToString()
foreach ($Server in $Top4.ComputerName) {
Invoke-Command -ComputerName $Server -ScriptBlock {HOSTNAME.EXE}
}
I'm not married to Invoke-Command in the last foreach but am having the same issues when I try to use psexec. Also, I'm running hostname.exe as a check to make sure I'm looping through the correct machines before I point it at my script.
Here's a streamlined version of your code, which heeds the comments on the question:
# Get all computers of interest.
$computers = Get-ADComputer -Filter 'ObjectClass -eq "Computer"' -SearchBase "OU=***,OU=***,OU=***,DC=***,DC=***" -SearchScope Subtree
# Get the computers' up-times in hours.
# The result will be [double] instances, but they're also decorated
# with .PSComputerName properties to identify the computer of origin.
$upTimes = Invoke-Command -ComputerName $computers.ConputerName {
((Get-Date) - (Get-CimInstance -Class Win32_OperatingSystem).LastBootUpTime).TotalHours
}
# Get the top 4 computers by up-time.
$top4 = $upTimes | Sort-Object -Descending | Select-Object -First 4
# Invoke a command on all these 4 computers in parallel.
Invoke-Command -ComputerName $top4.PSComputerName -ScriptBlock { HOSTNAME.EXE }
i got a client who wants to find all of the companys installed programs i wrote a script but i dont want the script show me the same same programs for each comuter every time,i want to see overall installations
$computers = get-adcomputers -filter *
foreach($computer in $computers){
Get-ItemProperty
HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* |
Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Format-
Table –AutoSize}
I did not test this, but you can try
$computers = (Get-ADComputer -Filter *).DNSHostName # or use .Name or .CN
$software = Invoke-Command -ComputerName $computers {
Get-ItemProperty -Path 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
}
$software | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate -Unique |
Format-Table -AutoSize
P.S.1 You need to have admin permissions on all computers to do this
P.S.2 Don't forget there is also HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
Apparently you are running into problems where computers are off-line.
To overcome that, you need to add a loop so you can test if a machine is reachable or not.
$computers = (Get-ADComputer -Filter *).Name # or use .CN
# loop through the collection and (if reachable) get the software list
$result = foreach ($computer in $computers) {
# test if the computer is online
if (Test-Connection -ComputerName $computer -Count 1 -Quiet) {
# output the properties you need to get collected in variable $result
Invoke-Command -ComputerName $computer {
Get-ItemProperty -Path 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
} | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate
}
else {
Write-Warning "Computer $computer is off-line"
}
}
$software = $result | Select-Object * -Unique
# output to console
$software | Format-Table -AutoSize
# output to CSV file
$software | Export-Csv -Path 'D:\Software.csv' -NoTypeInformation
I'm trying to loop through a list of Windows machines in the domain to call for Disk Cleanup however, because the way the list is returned it's not executing the ForEach statement with the name substituted. Can anyone provide some guidance on how to make this work?
Get-ADComputer -Filter {OperatingSystem -Like "*windows*"} -Property * | Format-Table Name | foreach {Echo "psexec \\$ cleanmgr /sagerun:1"}
something like this
$computers = get-Content c:\computers.txt
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}}
I got it working exactly the way I had wanted and with specific OU.
ForEach ($COMPUTER in (Get-ADComputer -Filter * | Where-Object {$_.DistinguishedName -Like "*OU=Employee's Computers,DC=company,DC=com"} | Select-Object -ExpandProperty Name)) { .\psexec \\$COMPUTER cleanmgr /sagerun:1 }
I've got a script which goes out to computer names from a text file and then polls the services on those machines and writes the results back to a text file.
Here's what I currently have set up:
$Computers = Get-Content computername.txt
$Output = Foreach ($Computer in $Computers) {
Write-Output "`n" "Status of $Computer".ToUpper()
Get-WMIobject -Computername $Computer win32_service | Where-Object {$_.startname -ne "LocalSystem" -and $_.startname -ne "localservice" -and $_.startname -notlike "*AUTHORITY*"} | ft name, startname, startmode, state
}
$Output | Out-File ServiceReport.txt
Is there a way to run multiple sessions at once instead of processing this incrementally? IE - send the command to all the systems at once and then receive the response and record appropriately? I've looked in to Invoke-Command and a few other things but have yet to improve time, get results to write back to the file, or get those options working generally.
Get-WMIObject will multi-thread. You just need to give it more than one computer at a time to work with:
$Computers = Get-Content computername.txt
Get-WMIobject -Computername $Computers win32_service |
Where-Object {$_.startname -ne "LocalSystem" -and $_.startname -ne "localservice" -and $_.startname -notlike "*AUTHORITY*"} |
ft PSComputerName, name, startname, startmode, state |
Out-string |
Out-File ServiceReport.txt
You'll lost the ability to have that "Status of $Computer" line before each one but you can make up for it by including the computer name in the selected objects.
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 } }