I was trying to execute x different server restarts with a PowerShell script, but as soon as it hits my for loop it just ends and I don't know where I made the mistake.
Code:
$x = Read-Host "How much you want to restart"
for ($i=0; $i -eq $x; $i++)
{
$name = read-host "Enter Servername" $i "to restart"
Restart-Computer -ComputerName $name -wait
write-host "Server" $name "restarded"
}
Edit: thx to the answer I corrected the $i++ but still it does end immediately after the number is entered.
arco444's answer should fix your problem. However, maybe it's more interesting to write your code like this:
$serverList = #()
Do {
$name = read-host "Enter Servername to restart"
if ($name) {
$serverList += $name
}
} While ($name)
Foreach ($server in $serverList) {
Restart-Computer -ComputerName $server -wait
write-host "Server $server restarded"
}
This will keep asking for a servername, until you provide none. Then it will go ahead and restart the servers.
Or:
Do {
$name = read-host "Enter Servername to restart"
if ($name) {
Restart-Computer -ComputerName $name -wait
write-host "Server $name restarded"
}
} While ($name)
It might be helpful to encapsulate the rebooting code separately from the gathering of COMPUTERNAMEs. This would make it possible for the list of computers to be created from a script prompting for names or any other method.
When you are confident that the correct computers will be rebooted, remove the -WhatIf from the Restart-Computer cmdlet.
=== Do-Reboot.ps1
[cmdletbinding()]
Param (
[Parameter(Mandatory = $true, Position = 0)]
[string[]]$ComputerName
)
foreach ($Computer in $ComputerName) {
Write-Information "reboot $Computer"
Restart-Computer -ComputerName $Computer -Wait -WhatIf
}
Then, it can be used:
Do-Reboot -ComputerName SERVER1,SERVER2,SERVER3
If you want to see something on the console for each reboot, use:
Do-Reboot -ComputerName SERVER1,SERVER2,SERVER3 -InformationAction Continue
If you have the list of servers in a text file, this could be used.
Do-Reboot -ComputerName $(Get-Content -Path '.\rebootlist.txt')
Use #Michael B.'s answer to improve your code, but below you'll find the explanation why your code doesn't work as expected.
Your script will never enter the for loop due to incorrect condition.
Here you expect to execute the command block when $i is equal to $x:
for ($i=0; $i -eq $x; $i++)
As you initialize $i with the value 0, it'll check that the condition is false and never execute command block.
If you want the command block to be executed x times, your loop should look like:
for ($i=0; $i -lt $x; $i++) {
# command block
}
Alternatively, you can use -ne instead of -lt.
Examples to check:
Working version
$x = 5
for ($i=0; $i -lt$x; $i++) {$i}
Output:
0
1
2
3
4
Non-working version
$x = 5
for ($i=0; $i -eq $x; $i++) {$i}
Output empty
Related
My goal is to check the services on multiple remote machines to make sure they are running, and starting them if they are not. I would like to modify the below code to add the ability to ask the user before proceeding to the next $computerName, to display and confirm the status of group of $serviceNames that is being passed through the function.
In a text file servers.txt the contents are as follows:
server1-serviceA,ServiceB,ServiceC
server2-serviceD,ServiceE,ServiceF
server3-serviceG,ServiceH,ServiceI
And here is the powershell script, checking different services for each server using the split function
$textFile = Get-Content C:\temp\servers.txt
foreach ($line in $textFile) {
$computerName = $line.split("-")[0] #Getting computername by using Split
$serviceNames = $line.split("-")[1] #Getting Service names by using split
foreach ($serviceName in $serviceNames.split(",")) {
# Again using split to handle multiple service names
try {
Write-Host " Trying to start $serviceName in $computerName"
Get-Service -ComputerName $computerName -Name $serviceName | Start-Service -ErrorAction Stop
Write-Host "SUCCESS: $serviceName has been started"
}
catch {
Write-Host "Failed to start $serviceName in $computerName"
}
}
}
You could use the PSHostUserInterface.PromptForChoice method for this, here is an example of how you can implement it:
# Clear this variable before in case it's already populated
$choice = $null
foreach ($line in $textFile) {
$computerName = $line.split("-")[0]
$serviceNames = $line.split("-")[1]
# If previous choice was not equal to 2 (Yes to All), ask again
if($choice -ne 2) {
$choice = $host.UI.PromptForChoice(
"my title here", # -> Title
"Continue with: ${computerName}?", # -> Message
#("&Yes", "&No", "Yes to &All"), # -> Choices
0 # -> Default Choice (Yes)
)
}
# If choice was 1 (No), stop the script here
if($choice -eq 1) { return }
# Same logic here
}
I have:
for($i = 1 ; $i -le 3; $i++)
{
Start-Process powershell.exe
}
but I don't know how I would make the new windows run a ping command. Could be done with an extra script but have no idea. Thanks
Start-Process has an -ArgumentList parameter that can be used to pass arguments to the new PowerShell process.
powershell.exe and pwsh.exe have a -Command parameter that can be used to pass them a command.
You can combine the two like this:
for ($i = 1; $i -le 3; $i++)
{
Start-Process powershell.exe -ArgumentList '-NoExit',"-Command ping 127.0.0.$i"
}
If you don't use the -NoExit parameter the window will close as soon as the ping command finishes.
As mentioned in the comments to the question it is also possible to ping multiple hosts using the Test-Connection command like this:
Test-Connection -TargetName 127.0.0.1,127.0.0.2
This has a downside though in that it seems to ping one after the other rather than doing it in parallel.
Another way to do much the same thing in parallel, and probably more PowerShell style is to use jobs:
$jobs = #()
for ($i = 1; $i -le 3; $i++)
{
$jobs += Start-ThreadJob -ArgumentList $i { PARAM ($i)
Test-Connection "127.0.0.$i"
}
}
Wait-Job $jobs
Receive-Job $jobs -Wait -AutoRemoveJob
Note: Start-ThreadJob is newish. If you're still stuck on version 5 of PowerShell that comes with Windows use Start-Job instead, it spawns new processes where as Start-ThreadJob doesn't.
Nitpickers' corner
For those in the comments saying that appending to an array is slow. Strictly a more PowerShell way of doing this is given below. For three items, however, you won't be able to measure the difference and the readability of the code is way lower. It's also rather diverging from the original question.
1..3 | % { Start-ThreadJob -ArgumentList $_ { PARAM($i) Test-Connection "127.0.0.$i" } } | Wait-Job | Receive-Job -Wait -AutoRemoveJob
Here's a pinger script I have to watch multiple computers.
# pinger.ps1
# example: pinger comp001
# pinger $list
param ($hostnames)
#$pingcmd = 'test-netconnection -port 515'
$pingcmd = 'test-connection'
$sleeptime = 1
$sawup = #{}
$sawdown = #{}
foreach ($hostname in $hostnames) {
$sawup[$hostname] = $false
$sawdown[$hostname] = $false
}
#$sawup = 0
#$sawdown = 0
while ($true) {
# if (invoke-expression "$pingcmd $($hostname)") {
foreach ($hostname in $hostnames) {
if (& $pingcmd -count 1 $hostname -ea 0) {
if (! $sawup[$hostname]) {
echo "$([console]::beep(500,300))$hostname is up $(get-date)"
# [pscustomobject]#{Hostname = $hostname; Status = 'up'; Date = get-date} # format-table waits for 2 objects
$sawup[$hostname] = $true
$sawdown[$hostname] = $false
}
} else {
if (! $sawdown[$hostname]) {
echo "$([console]::beep(500,300))$hostname is down $(get-date)"
# [pscustomobject]#{Hostname = $hostname; Status = 'down'; Date = get-date}
$sawdown[$hostname] = $true
$sawup[$hostname] = $false
}
}
}
sleep $sleeptime
}
Example usage (it beeps):
.\pinger comp001,comp002
comp001 is up 07/13/2022 12:07:59
comp002 is up 07/13/2022 12:08:00
I have hybrid setup where shared mailboxes are getting created on-prem and synced through to Exchange Online in a span of couple of minutes.
My routine is to create a shared mailbox on-prem and then convert it, populate, enable messagecopy, etc. through Connect-ExchangeOnline.
I want to have a tiny script to check if it synced to EO or not.
I've tried several different ways and seemingly this one should work, but unfortunately it breaks after both success or error without attempting to run get-mailbox in 10 seconds as I expect it to.
Please review and advise.
$ErrorActionPreference = 'SilentlyContinue'
$ID = "xxx#yyy"
$i=0
while ($i -le 10) {
try {
Get-Mailbox $ID
break
}
catch {
$i++
$i
Start-Sleep 10
}
}
As commented, to catch also non-terminating exceptions, you must use -ErrorAction Stop.
But why not simply do something like
$ID = "xxx#yyy"
for ($i = 0; $i -le 10; $i++) { # # loop 11 attempts maximum
$mbx = Get-Mailbox -Identity $ID -ErrorAction SilentlyContinue
if ($mbx) {
Write-Host "Mailbox for '$ID' is found" -ForegroundColor Green
break
}
Write-Host "Mailbox for '$ID' not found.. Waiting 10 more seconds"
Start-Sleep -Seconds 10
}
Or, if you want to use try{..} catch{..}
$ID = "xxx#yyy"
for ($i = 0; $i -le 10; $i++) { # loop 11 attempts maximum
try {
$mbx = Get-Mailbox -Identity $ID -ErrorAction Stop
Write-Host "Mailbox for '$ID' is found" -ForegroundColor Green
$i = 999 # exit the loop by setting the counter to a high value
}
catch {
Write-Host "Mailbox for '$ID' not found.. Waiting 10 more seconds"
Start-Sleep -Seconds 10
}
}
This is my first program in powershell, Im trying to get from the user input and then pinging the IP address or the hostname, Creating text file on the desktop.
But if the user wants the add more than one IP I get into infinite loop.
Here Im asking for IP address.
$dirPath = "C:\Users\$env:UserName\Desktop"
function getUserInput()
{
$ipsArray = #()
$response = 'y'
while($response -ne 'n')
{
$choice = Read-Host '
======================================================================
======================================================================
Please enter HOSTNAME or IP Address, enter n to stop adding'
$ipsArray += $choice
$response = Read-Host 'Do you want to add more? (y\n)'
}
ForEach($ip in $ipsArray)
{
createFile($ip)
startPing($ip)
}
}
Then I creating the file for each IP address:
function createFile($ip)
{
$textPath = "$($dirPath)\$($ip).txt"
if(!(Test-Path -Path $textPath))
{
New-Item -Path $dirPath -Name "$ip.txt" -ItemType "file"
}
}
And now you can see the problem, Because I want the write with TIME format, I have problem with the ForEach loop, When I start to ping, And I cant reach the next element in the array until I stop
the cmd.exe.
function startPing($ip)
{
ping.exe $ip -t | foreach {"{0} - {1}" -f (Get-Date), $_
} >> $dirPath\$ip.txt
}
Maybe I should create other files ForEach IP address and pass params?
Here's a old script I have. You can watch a list of computers in a window.
# pinger.ps1
# example: pinger yahoo.com
# pinger c001,c002,c003
# $list = cat list.txt; pinger $list
param ($hostnames)
#$pingcmd = 'test-netconnection -port 515'
$pingcmd = 'test-connection'
$sleeptime = 1
$sawup = #{}
$sawdown = #{}
foreach ($hostname in $hostnames) {
$sawup[$hostname] = $false
$sawdown[$hostname] = $false
}
#$sawup = 0
#$sawdown = 0
while ($true) {
# if (invoke-expression "$pingcmd $($hostname)") {
foreach ($hostname in $hostnames) {
if (& $pingcmd -count 1 $hostname -ea 0) {
if (! $sawup[$hostname]) {
echo "$([console]::beep(500,300))$hostname is up $(get-date)"
$sawup[$hostname] = $true
$sawdown[$hostname] = $false
}
} else {
if (! $sawdown[$hostname]) {
echo "$([console]::beep(500,300))$hostname is down $(get-date)"
$sawdown[$hostname] = $true
$sawup[$hostname] = $false
}
}
}
sleep $sleeptime
}
pinger microsoft.com,yahoo.com
microsoft.com is down 11/08/2020 17:54:54
yahoo.com is up 11/08/2020 17:54:55
Have a look at PowerShell Jobs. Note that there are better and faster alternatives (like thread jobs, runspaces, etc), but for a beginner, this would be the easiest way. Basically, it starts a new PowerShell process.
A very simple example:
function startPing($ip) {
Start-Job -ScriptBlock {
param ($Address, $Path)
ping.exe $Address -t | foreach {"{0} - {1}" -f (Get-Date), $_ } >> $Path
} -ArgumentList $ip, $dirPath\$ip.txt
}
This simplified example does not take care of stopping the jobs. So depending on what behavior you want, you should look that up.
Also, note there there is also PowerShell's equivalent to ping, Test-Connection
$fullnamexp = ((net user $winxp /domain | Select-String "Full Name") -replace "Full Name","").Trim();
If $winxp cannot be found, the command will hang, is there a timeout I can use with this to make it move on after 5-10 seconds? Not sure where I would put it.
Edit- I use this to pull the username:
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $tag1)
$key = $reg.OpenSubKey('SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinLogon')
$winxp = $key.GetValue('DefaultUserName') -replace '^.*?\\'
$winxp is then a login name such as ajstepanik then I put it into: $fullnamexp = ((net user $winxp /domain | Select-String "Full Name") -replace "Full Name","").Trim();
1.21.2014 Update
$timeoutSeconds = 5
$code = {
((net user $winxp /domain | Select-String "Full Name") -replace "Full Name","").Trim(); # your commands here, e.g.
}
$j = Start-Job -ScriptBlock $code
if (Wait-Job $j -Timeout $timeoutSeconds) { $fullnamexp = Receive-Job $j }
Remove-Job -force $j
While #mjolinor may have indeed provided you an alternative approach, here is a direct answer to your general question: how do you force a timeout in PowerShell?
Wrap whatever you wish to time-limit in a script block, run that as a job, then use the Wait-Job cmdlet to time-limit the operation. Wait-Job will return either at the end of the timeout period or when the script block completes, whichever occurs first. After Wait-Job returns, you can examine the job state ($j.state) to determine whether it was interrupted or not, if it matters to you.
$timeoutSeconds = 5 # set your timeout value here
$j = Start-Job -ScriptBlock {
# your commands here, e.g.
Get-Process
}
"job id = " + $j.id # report the job id as a diagnostic only
Wait-Job $j -Timeout $timeoutSeconds | out-null
if ($j.State -eq "Completed") { "done!" }
elseif ($j.State -eq "Running") { "interrupted" }
else { "???" }
Remove-Job -force $j #cleanup
2014.01.18 Update
Here is a bit more streamlining approach that also includes the practical step of getting information out of the script block with Receive-Job, assuming what you want is generated on stdout:
$timeoutSeconds = 3
$code = {
# your commands here, e.g.
Get-ChildItem *.cs | select name
}
$j = Start-Job -ScriptBlock $code
if (Wait-Job $j -Timeout $timeoutSeconds) { Receive-Job $j }
Remove-Job -force $j
You can use Start-Sleep to pause the script:
Start-Sleep -s 5
net doesn't explicitly allow you to set a time out on it's operations, but you could check out this link on changing the ipv4 timeout for your sockets:
http://www.cyberciti.biz/tips/linux-increasing-or-decreasing-tcp-sockets-timeouts.html
The only thing else I could imagine is spawning a worker thread but I don't even know if that's possible in bash, I'm not fluid enough in it to answer that; plus it opens you up to sync problems and all sorts of multi threaded issues beyond what you're trying to accomplish quickly in a bash script to begin with! :P
Does this help?
$query = (dsquery user -samid $winxp)
if ($query) {$fullnamexp = ($query | dsget user -display)[1].trim()}
$fullnamexp
This solution doesn't work for me. remove-job -force $j takes over 5 seconds in this example.
$timeoutseconds = 1
$start = get-date
$j = start-job -scriptblock { Resolve-DnsName 1.1.1.1 }
if (wait-job $j -timeout $timeoutseconds) { $fullnamexp = receive-job $j }
remove-job -force $j
(get-date) - $start
Days : 0
Hours : 0
Minutes : 0
Seconds : 5
Milliseconds : 342
Ticks : 53426422
TotalDays : 6.18361365740741E-05
TotalHours : 0.00148406727777778
TotalMinutes : 0.0890440366666667
TotalSeconds : 5.3426422
TotalMilliseconds : 5342.6422
Here's a simple timeout example with notepad:
notepad
if (-not $(wait-process notepad 10; $?)) { stop-process -name notepad }
$watchdog = 10 #seconds
$start_time = Get-Date
$j = Start-Job -ScriptBlock{
#timeout command
if ($true) {
$i = 0
while($true) {
Write-Host "Count: $i"
Start-Sleep -Milliseconds 100
$i++
}
}
write-host "Hello"
}
while($true) {
if ($j.HasMoreData) {
Receive-Job $j
Start-Sleep -Milliseconds 200
}
$current = Get-Date
$time_span = $current - $start_time
if ($time_span.TotalSeconds -gt $watchdog) {
write-host "TIMEOUT!"
Stop-Job $j
break
}
if (-not $j.HasMoreData -and $j.State -ne 'Running') {
write-host "Finished"
break
}
}
Remove-Job $j