Help me please figure out how asynchronous loop execution works.
In many forums, examples are very cumbersome and not understandable
During execution, the program window freezes, and at the end of the cycle it shows the final number
for example
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[xml]$XAML = #"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Рассылка интернет-трафика" Height="200" Width="300" ResizeMode="NoResize">
<Grid Width="300">
<TextBox Name="text_result" TextWrapping="Wrap" Margin="0,0,0,0" VerticalAlignment="Top" Height="31" Width="200" />
<Button Name="button_start" Content="Start" Height="31" Margin="0,50,0,0" VerticalAlignment="Top" Width="200" />
<Button Name="button_close" Content="Close" Height="31" Margin="0,100,0,0" VerticalAlignment="Top" Width="200"/>
</Grid>
</Window>
"#
#Read XAML
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
try{$Form=[Windows.Markup.XamlReader]::Load( $reader )}
catch{Write-Host "Unable to load Windows.Markup.XamlReader"; exit}
# Store Form Objects In PowerShell
$xaml.SelectNodes("//*[#Name]") | ForEach-Object {Set-Variable -Name ($_.Name) -Value $Form.FindName($_.Name)}
$button_start.Add_Click({
for ($row = 2; $row -le 1000; $row++) {
$text_result.Text = $row
}
})
#Загружаем главную форму
$Form.ShowDialog() | out-null
It took me quite a while to figure this one out. Let me preface by saying that Adam the Automator has a fantastic breakdown:
https://adamtheautomator.com/building-asynchronous-powershell-functions/
The easiest way to think about executing an asynchronous loop is to think about tricking powershell. In short, you just want it to do something in the background while your script is running. So, you just need to turn the loop you would like into a script block and use start-job, a.k.a. &.
Let's use a common task, such as looking through a bunch of files. Here, we'll use gci.
$scriptblock = {
param ($folder)
Get-ChildItem -Path $folder}
Start-job -ScriptBlock $scriptblock -ArgumentList "C:\Windows" -name "stackexample"
The name is important so that you can reference it later. Now you have a job in the background, and you're good to continue with your script. But what happens if you want to see if the job is done? There's a command for checking on the status of your job (code below).
Get-Job -Name stackexample
One thing to note, make sure to CLOSE your jobs once they're done.
$status = (Get-Job -Name stackexample).state
if ($status -eq "completed")
{
Remove-Job -Name stackexample
}
Wait, I need to know what those folders were! No problem. We just need to tell the scriptblock to output data (write-host, for example). Then, we receive the job before we close it. So a full circuit might look like this:
$scriptblock = {
param ($folder)
$folders = Get-ChildItem -Path $folder
write-host $folders
}
Start-job -ScriptBlock $scriptblock -ArgumentList "C:\Windows" -name "stackexample"
Start-Sleep -Seconds 5
$status = (Get-Job -Name stackexample).state
if ($status -eq "completed")
{
Receive-Job -Name StackExample -Wait
Remove-Job -Name stackexample
}
You can get so much fancier from here, but that is the core of what 'asynchronous code execution' is - telling powershell to do something time consuming while you're doing other stuff.
As applied to your code, you can use the button click to set off a job and run in the background, then receive the final results.
$xaml.SelectNodes("//*[#Name]") | ForEach-Object {Set-Variable -Name ($_.Name) -Value $Form.FindName($_.Name)}
$scriptblock = {
for ($row = 2; $row -le 1000; $row++)
{
$result = $row
}
return $result
}
$button_start.Add_Click({
Start-job -ScriptBlock $scriptblock -Name scriptblock
While ((Get-Job -Name scriptblock).State -ne "Completed")
{
start-sleep 1
}
$results = receive-job -Name scriptblock -keep
remove-job -Name scriptblock
$text_result.Text = $results
})
Above the button click we've defined the script block. When the button is clicked, it kicks off the job and then watches it with the while loop. Once the job is finished, we can extract the result (done here with return $result in the scriptblock). Receive the job and you have your final variable which you can now display for the user.
With no cycle.
The form is just a name in this case. You can remove all GUI elements from it if you want.
$Script:SyncHashTable = [Hashtable]::Synchronized(#{})
$RunSpace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
$RunSpace.ApartmentState = "STA"
$RunSpace.ThreadOptions = "ReuseThread"
$RunSpace.Open()
$RunSpace.SessionStateProxy.SetVariable("SyncHashTable", $Script:SyncHashTable)
$PowerShellCmd = [Management.Automation.PowerShell]::Create().AddScript({
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$Script:SyncHashTable.objForm = New-Object System.Windows.Forms.Form
$Script:SyncHashTable.InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState
$Script:SyncHashTable.objForm.Size = New-Object System.Drawing.Size(340, 200)
$Script:SyncHashTable.objForm.Name = "objForm"
$Script:SyncHashTable.objForm.Text = "Test form"
$Script:SyncHashTable.objLabel = New-Object System.Windows.Forms.Label
$Script:SyncHashTable.objLabel.Location = New-Object System.Drawing.Size(10,20)
$Script:SyncHashTable.objLabel.Size = New-Object System.Drawing.Size(320,20)
$Script:SyncHashTable.objLabel.Text = "Enter the data and press the OK button or key Enter"
$Script:SyncHashTable.objForm.Controls.Add($Script:SyncHashTable.objLabel)
$Script:SyncHashTable.objTextBox = New-Object System.Windows.Forms.TextBox
$Script:SyncHashTable.objTextBox.Location = New-Object System.Drawing.Size(10,40)
$Script:SyncHashTable.objTextBox.Size = New-Object System.Drawing.Size(300,20)
$Script:SyncHashTable.objForm.Controls.Add($Script:SyncHashTable.objTextBox)
$Script:SyncHashTable.objForm.KeyPreview = $True
$Script:SyncHashTable.objForm.Add_KeyDown( { if ($_.KeyCode -eq "Enter") { $Script:SyncHashTable.X = $Script:SyncHashTable.objTextBox.Text;$Script:SyncHashTable.objForm.Close() } })
$Script:SyncHashTable.objForm.Add_KeyDown( { if ($_.KeyCode -eq "Escape") { $objForm.Close() } })
$Script:SyncHashTable.OKButton = New-Object System.Windows.Forms.Button
$Script:SyncHashTable.OKButton.Location = New-Object System.Drawing.Size(75,120)
$Script:SyncHashTable.OKButton.Size = New-Object System.Drawing.Size(75,23)
$Script:SyncHashTable.OKButton.Text = "OK"
$Script:SyncHashTable.OKButton.Add_Click( { $Script:SyncHashTable.X = $Script:SyncHashTable.objTextBox.Text;$Script:SyncHashTable.objForm.Close() } )
$Script:SyncHashTable.objForm.Controls.Add($Script:SyncHashTable.OKButton)
$Script:SyncHashTable.InitialFormWindowState = $Script:SyncHashTable.objForm.WindowState
$Script:SyncHashTable.objForm.add_Load($Script:SyncHashTable.OnLoadForm_StateCorrection)
[void] $Script:SyncHashTable.objForm.ShowDialog()
})
$PowerShellCmd.Runspace = $RunSpace
$obj=$PowerShellCmd.BeginInvoke()
#Time to retrieve our missing object $AsyncObject
$BindingFlags = [Reflection.BindingFlags]'nonpublic','instance'
$Field = $PowerShellCmd.GetType().GetField('invokeAsyncResult',$BindingFlags)
$AsyncObject = $Field.GetValue($PowerShellCmd)
Write-Host 'Here is your code that will be executed in parallel with the form code'
Write-Host '(any length and execution time)'
Write-Host "`$PowerShellCmd.EndInvoke(`$AsyncObject) will wait for results from the form,"
Write-Host "or pick them up if they are ready."
Write-Host "=================================="
#Closing the execution space when getting the result
$PowerShellCmd.EndInvoke($AsyncObject)
'This is the result of executing the form code: {0}' -f $Script:SyncHashTable.X
Related
I've been working on a simple proof of concept / template of a script in which I have the default Runspace to run heavy tasks and another one to run a responsive GUI.
I have tested various methods to be able to communicate the runspaces. At first I tried the Control.Invoke but some opinions on different forums and a weird behaviour on tests led me to use a message based intercomunication based on a Synchronized HashTable. ProgressBar works with control.Invoke, but, executing some other actions, like disabling multiple buttons on a form performs very slow.
1st problem: I would like to show a progressbar (marquee) when the task is executing, changing its visible state. However, the progressbar is showed when the script runs on ISE, but not when it does on console. I think it is because main runspace is busy, but I don't understand how it could affect the GUI runspace...
2nd: on the script I post below, I'm using scriptblocks through a stack variable to pass the commands between runspaces. Then, each runspace (main runspace does it through pooling and GUI through a timer) checks if a task is pending of execution in the stack, and, if so, executes it. However if I would want to call a function declared on the other runspace (Test-OutOfMainScopeUiFunction in this example), I couldn't. I would get a runtime error on the scriptblock. I have thought solutions to this like:
-Importing functions on both runspaces
-Making functions global or use functon delegates¿?
-Passing a string with the commands to execute in spite of a scriptblock -> Using this one at the moment, but don't like very much... error prone
Any solution to the progress bar problem or script improvements will be appreciated. Thanks!
$global:x = [Hashtable]::Synchronized(#{})
$global:x.MainDispatcher = new-object system.collections.stack
$global:x.UiDispatcher = new-object system.collections.stack
$global:x.GuiExited = $false
$formSB = {
[reflection.assembly]::LoadWithPartialName("System.Windows.Forms")
[reflection.assembly]::LoadWithPartialName("System.Drawing")
Function Test-OutOfMainScopeUiFunction
{
$x.Host.Ui.WriteLine("Test-OutOfMainScopeUiFunction Executed")
}
Function Execute-OnMainRs
{
param(
[Parameter(Mandatory=$true)]
[ScriptBlock]$ScriptBlock
)
$x.Host.Ui.WriteLine("`r`nAdding task")
$x.MainDispatcher.Push($ScriptBlock)
$x.Host.Ui.WriteLine("Task added")
$x.Host.Ui.WriteLine("Task: $($x.MainDispatcher)")
}
$form = New-Object System.Windows.Forms.Form
$button = New-Object System.Windows.Forms.Button
$button.Text = 'click'
$button.Dock = [System.Windows.Forms.DockStyle]::Right
$progressBar = (New-Object -TypeName System.Windows.Forms.ProgressBar)
$ProgressBar.Style = [System.Windows.Forms.ProgressBarStyle]::Marquee
$ProgressBar.MarqueeAnimationSpeed = 50
$ProgressBar.Dock = [System.Windows.Forms.DockStyle]::Bottom
$ProgressBar.Visible = $false
$label = New-Object System.Windows.Forms.Label
$label.Text = 'ready'
$label.Dock = [System.Windows.Forms.DockStyle]::Top
$timer=New-Object System.Windows.Forms.Timer
$timer.Interval=100
$timer.add_Tick({
if($x.UiDispatcher.Count -gt 0)
{
& $($x.UiDispatcher.Pop())
}
})
$form.Controls.add($label)
$form.Controls.add($button)
$form.Controls.add($progressBar)
Add-Member -InputObject $form -Name Label -Value $label -MemberType NoteProperty
Add-Member -InputObject $form -Name ProgressBar -Value $progressBar -MemberType NoteProperty
$button.add_click({
Execute-OnMainRs -ScriptBlock {
write-host "MainRS: Long Task pushed from the UI started on: $(Get-Date)"
start-sleep -s 2
write-host "MainRS: Long Task pushed from the UI finished on: $(Get-Date)"
}
})
$form.add_closed({ $x.GuiExited = $true })
$x.Form = $form
$timer.Start()
$form.ShowDialog()
}
Function Execute-OnRs
{
param(
[Parameter(Mandatory=$true)]
[ScriptBlock]$ScriptBlock
)
$x.Host = $Host
$rs = [RunspaceFactory]::CreateRunspace()
$rs.ApartmentState,$rs.ThreadOptions = "STA","ReUseThread"
$rs.Open()
$rs.SessionStateProxy.SetVariable("x",$x)
$ps = [PowerShell]::Create().AddScript($ScriptBlock)
$ps.Runspace = $rs
$handle = $ps.BeginInvoke()
#Almacenar variables del RS
$x.Handle = $handle
$x.Ps = $ps
}
Function Execute-OnUiRs
{
param(
[Parameter(Mandatory=$true)]
[ScriptBlock]$ScriptBlock
)
$x.UiDispatcher.Push($ScriptBlock)
}
Function Dispatch-PendingJobs
{
while($global:x.GuiExited -eq $false) {
if($global:x.MainDispatcher.Count -gt 0)
{
Execute-OnUiRs -ScriptBlock {
$msg = "UIRS: MainThread informs: Long Task started on $(Get-Date)."
$global:x.Form.Label.Text = $msg
$global:x.Form.ProgressBar.Visible = $true
$x.host.ui.WriteLine($msg)
#Next line throws an error visible on UI runspace error stream
Test-OutOfMainScopeUiFunction
}
& $($global:x.MainDispatcher.Pop())
Execute-OnUiRs -ScriptBlock {
$msg = "UIRS: MainThread informs: Long Task finished on $(Get-Date)."
$global:x.Form.Label.Text = $msg
$global:x.Form.ProgressBar.Visible = $false
$x.host.ui.WriteLine($msg)
}
write-host "UI Streams: $($global:x.Ps.Streams |out-string)"
}
else
{
start-sleep -m 100
}
}
}
Found the solution... http://community.idera.com/powershell/powertips/b/tips/posts/enabling-visual-styles
VisualStyles must be enabled first. The problem is not related with runspaces stuff. This is a brief and clearer code example taken from Power Shell marquee progress bar not working with the fix.
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$window = New-Object Windows.Forms.Form
$window.Size = New-Object Drawing.Size #(400,75)
$window.StartPosition = "CenterScreen"
$window.Font = New-Object System.Drawing.Font("Calibri",11,[System.Drawing.FontStyle]::Bold)
$window.Text = "STARTING UP"
$ProgressBar1 = New-Object System.Windows.Forms.ProgressBar
$ProgressBar1.Location = New-Object System.Drawing.Point(10, 10)
$ProgressBar1.Size = New-Object System.Drawing.Size(365, 20)
$ProgressBar1.Style = "Marquee"
$ProgressBar1.MarqueeAnimationSpeed = 20
$window.Controls.Add($ProgressBar1)
$window.ShowDialog()
I'm an active reader of StackOverflow as it usually helps me resolve all my issues.
But for my first question ever I'll ask your help about runspaces in Powershell, specifically for memory management.
I created some PS apps with GUI and now I use runspaces to avoid hang of the GUI when big operations are running.
My problem is that I can't manage to dispose my runspaces when they are finished.
Regarding the code below, it's a simple script to try to understand how I can dispose of my runspaces. I tried to incorporate a unique runspace to monitor and dispose the app runspaces, I inspired myself from a proxb's script (huge fan btw) but it doesn't seems to work.
Everytime I execute the runspace I gain 10Mb of memory, huge leak!
Can you help me to resolve this problem please ?
[xml]$xaml = #'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Runspace" FontFamily="Calibri" Height="400" Width="400" FontSize="14">
<Grid Margin="10,10,10,10">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox x:Name="TB_Results" Grid.Row="0" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"/>
<Button x:Name="BT_Run" Grid.Row="2" Content="Run" Height="30"/>
</Grid>
</Window>
'#
# Inits
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName PresentationCore
Add-Type -AssemblyName WindowsBase
$gui = [hashtable]::Synchronized(#{})
$reader = (New-Object Xml.XmlNodeReader $xaml)
$gui.Window = [Windows.Markup.XamlReader]::Load($reader)
$xaml.SelectNodes("//*[#*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach-Object { $gui.Add($_.Name,$gui.Window.FindName($_.Name)) }
$Script:Jobs = [system.collections.arraylist]::Synchronized((New-Object System.Collections.ArrayList))
$Script:JobCleanup = [hashtable]::Synchronized(#{ Host = $host })
# Test function
function RunFunc {
$newRunspace = [runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("gui",$gui)
$Code = {
$memupdate = "Memory: $($(Get-Process -Id $PID).PrivateMemorySize64 / 1mb) mb"
$gui.Window.Dispatcher.invoke("Normal",[action]{
$gui.TB_Results.Text = "$memupdate`r`n" + $gui.TB_Results.Text
})
}
$Powershell = [powershell]::Create().AddScript($Code)
$Powershell.Runspace = $newRunspace
[void]$Script:Jobs.Add((
[PSCustomObject]#{
PowerShell = $PowerShell
Runspace = $PowerShell.BeginInvoke()
}
))
}
# Background Runspace to clean up jobs
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("jobs",$Script:Jobs)
$Code = {
$JobCleanup = $true
do {
foreach($runspace in $jobs) {
if ($runspace.Runspace.isCompleted) {
$runspace.powershell.EndInvoke($runspace.Runspace) | Out-Null
$runspace.powershell.dispose()
$runspace.Runspace = $null
$runspace.powershell = $null
}
}
$temphash = $jobs.clone()
$temphash | Where-Object { $_.runspace -eq $Null } | ForEach-Object { $jobs.remove($_) }
Start-Sleep -Seconds 1
} while ($JobCleanup)
}
$Powershell = [powershell]::Create().AddScript($Code)
$Powershell.Runspace = $newRunspace
$PowerShell.BeginInvoke()
# gui events
$gui.BT_Run.add_Click({ RunFunc })
$gui.Window.ShowDialog() | Out-Null
When you call $runspace.PowerShell.Dispose(), the PowerShell instance is disposed, but the runspace that it's tied to is not automatically disposed, you'll need to do that first yourself in the cleanup task:
$runspace.powershell.EndInvoke($runspace.Runspace) | Out-Null
$runspace.powershell.Runspace.Dispose() # remember to clean up the runspace!
$runspace.powershell.dispose()
I'm new to Powershell. I need to capture the output from a command-line call in Powershell continuously and preferably send it to another function. My code currently waits until the external program is completely finished. Here's what I've got:
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = "DataSet.exe"
$startInfo.CreateNoWindow = $true
$startInfo.UseShellExecute = $false
$startInfo.RedirectStandardError = $true
$startInfo.RedirectStandartOutput = $true
$startInfo.Arguments = "1 off"
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $startInfo
$process.Start() | Out-Null
$stdOut = $process.StandardOutput.ReadToEnd()
$stdErr = $process.StandardError.ReadToEnd()
$process.WaitForExit()
process($stdOut)
process($stdErr)
# Do stuff with $process.ExitCode
Here's what I need
...
while (readOutput)
{
process($stdOut)
process($stdErr)
}
...
I would recommend using a background job for this kind of thing, e.g. like this:
$job = Start-Job -ScriptBlock { & "some.exe" }
while ($job.State -eq 'Running') {
Receive-Job $job -OutVariable $outValue -ErrorVariable $errValue
if ($outValue) { Do-SomethingWith $outValue }
ir ($errValue) { Do-OtherWith $errValue }
Start-Sleep -Milliseconds 200
}
Long story short, we are experiencing issues with some of our servers that cause crippling effects on them and I am looking for a way to monitor them, now I have a script that will check the RDP port to make sure that it is open and I am thinking that I want to use get-service and then I will return if it pulled any data or not.
Here is the issue I don't know how to limit the time it will wait for a response before returning false.
[bool](Get-process -ComputerName MYSERVER)
Although I like Ansgars answer with a time-limited job, I think a separate Runspace and async invocation fits this task better.
The major difference here being that a Runspace reuses the in-process thread pool, whereas the PSJob method launches a new process, with the overhead that that entails, such as OS/kernel resources spawning and managing a child process, serializing and deserializing data etc.
Something like this:
function Timeout-Statement {
param(
[scriptblock[]]$ScriptBlock,
[object[]]$ArgumentList,
[int]$Timeout
)
$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.Open()
$PS = [powershell]::Create()
$PS.Runspace = $Runspace
$PS = $PS.AddScript($ScriptBlock)
foreach($Arg in $ArgumentList){
$PS = $PS.AddArgument($Arg)
}
$IAR = $PS.BeginInvoke()
if($IAR.AsyncWaitHandle.WaitOne($Timeout)){
$PS.EndInvoke($IAR)
}
return $false
}
Then use that to do:
$ScriptBlock = {
param($ComputerName)
Get-Process #PSBoundParameters
}
$Timeout = 2500 # 2 and a half seconds (2500 milliseconds)
Timeout-Statement $ScriptBlock -ArgumentList "mycomputer.fqdn" -Timeout $Timeout
You could run your check as a background job:
$sb = { Get-Process -ComputerName $args[0] }
$end = (Get-Date).AddSeconds(5)
$job = Start-Job -ScriptBlock $sb -ArgumentList 'MYSERVER'
do {
Start-Sleep 100
$finished = (Get-Job -Id $job.Id).State -eq 'Completed'
} until ($finished -or (Get-Date) -gt $end)
if (-not $finished) {
Stop-Job -Id $job.Id
}
Receive-Job $job.Id
Remove-Job $job.Id
This is a known issue: https://connect.microsoft.com/PowerShell/feedback/details/645165/add-timeout-parameter-to-get-wmiobject
There is a workaround provided Here : https://connect.microsoft.com/PowerShell/feedback/details/645165/add-timeout-parameter-to-get-wmiobject
Function Get-WmiCustom([string]$computername,[string]$namespace,[string]$class,[int]$timeout=15)
{
$ConnectionOptions = new-object System.Management.ConnectionOptions
$EnumerationOptions = new-object System.Management.EnumerationOptions
$timeoutseconds = new-timespan -seconds $timeout
$EnumerationOptions.set_timeout($timeoutseconds)
$assembledpath = "\\" + $computername + "\" + $namespace
#write-host $assembledpath -foregroundcolor yellow
$Scope = new-object System.Management.ManagementScope $assembledpath, $ConnectionOptions
$Scope.Connect()
$querystring = "SELECT * FROM " + $class
#write-host $querystring
$query = new-object System.Management.ObjectQuery $querystring
$searcher = new-object System.Management.ManagementObjectSearcher
$searcher.set_options($EnumerationOptions)
$searcher.Query = $querystring
$searcher.Scope = $Scope
trap { $_ } $result = $searcher.get()
return $result
}
You can call the function like this:
get-wmicustom -class Win32_Process -namespace "root\cimv2" -computername MYSERVER –timeout 1
Here is my Powershell code that when i execute it, it will continually loop while waking up my internal sites
$urlFile = "C:\Users\lfouche\Desktop\url\url.txt"
foreach($site in Get-Content $urlFile){
function WakeUp([string] $url)
{
Write-Host "Waking up $url ..." -NoNewLine
$client = new-object system.net.WebClient
$client.UseDefaultCredentials = $true
$null = $client.OpenRead($url)
$client.Dispose()
Write-Host " Ok"
}
#(
Get-Content $urlFile
) | % { WakeUp $_ }
}
Well, your script can generally be improved, however I think the error is that you already iterate over Get-Content $urlFile within your foreach condition and also in the loop. Try this:
# define the wake up function once
function WakeUp
{
Param
(
[string] $url
)
Write-Host "Waking up $url ..." -NoNewLine
$client = new-object system.net.WebClient
$client.UseDefaultCredentials = $true
$null = $client.OpenRead($url)
$client.Dispose()
Write-Host " Ok"
}
$urlFile = "C:\Users\lfouche\Desktop\url\url.txt"
$sites = Get-Content $urlFile
foreach($site in $sites)
{
# call wake up for each site
WakeUp $site
}