Is this a good implementation of PowerShell runspaces? - multithreading

I have three components in my PowerShell script. I'm using a runspace for each : core, GUI and timer.
Core is for executing some actions, GUI for showing if action in core is completed and timer is needed for one action in core.
I'm not sure It's a clean/good solution... because I need to write some information with write-host and exchange some info between theses runspaces. Could you tell me a better way if not ?
Function StartRunSpaceForGui {
#-RunSpace for GUI---------------
$global:RunSpaceForGui=[runspacefactory]::CreateRunspace()
$global:RunSpaceForGui.ApartmentState="STA"
$global:RunSpaceForGui.ThreadOptions="ReuseThread"
$global:RunSpaceForGui.Open()
$global:HashForGui=[hashtable]::Synchronized(#{})
$global:RunSpaceForGui.SessionStateProxy.SetVariable("HashForGui",$HashForGui)
$global:ScriptForGui=[PowerShell]::Create().AddScript("$global:ProductPath\PS\Gui.ps1")
$global:ScriptForGui.RunSpace=$RunSpaceForGui
$global:HandleForGui=$ScriptForGui.BeginInvoke()
}
Function StartRunSpaceForEngine {
#-RunSpace for Engine---------------------#
$global:RunSpaceForEngine=[runspacefactory]::CreateRunspace()
$global:RunSpaceForEngine.Open()
$global:ScriptForEngine=[PowerShell]::Create().AddScript("$global:ProductPath\PS\Engine.ps1")
$global:ScriptForEngine.RunSpace=$RunSpaceForEngine
$global:HandleForEngine=$ScriptForEngine.BeginInvoke()
}
Function StartRunSpaceForTimer {
#-RunSpace for timer
$global:RunSpaceForTimer=[runspacefactory]::CreateRunspace()
$global:RunSpaceForTimer.Open()
$global:ScriptForTimer=[PowerShell]::Create().AddScript("$global:ProductPath\PS\Timer.ps1")
$global:ScriptForTimer.RunSpace=$RunSpaceForTimer
$global:HandleForTimer=$ScriptForTimer.BeginInvoke()
}
EDIT :
GUI thread :
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
#--Main Form Creation.
$HashForGui.MainForm=New-Object System.Windows.Forms.Form
$HashForGui.MainForm.Size=New-Object System.Drawing.Size($Width,$Height)
$HashForGui.MainForm.StartPosition="CenterScreen"
$HashForGui.MainForm.FormBorderStyle=[System.Windows.Forms.FormBorderStyle]::FixedDialog
$HashForGui.MainForm.MaximizeBox=$False
$HashForGui.MainForm.MinimizeBox=$False
$HashForGui.MainForm.ShowInTaskbar=$True
$HashForGui.MainForm.WindowState="Normal"
$HashForGui.MainForm.Text=$ProductName
$Icon=New-Object system.drawing.icon($ProductIconFilePath)
$HashForGui.MainForm.Icon=$Icon
#---WebBrowser Creation.
$HashForGui.MainWebBrowser=New-Object System.Windows.Forms.WebBrowser
$HashForGui.MainWebBrowser.Size=New-Object System.Drawing.Size($Width,$Height)
$HashForGui.MainWebBrowser.Location=New-Object System.Drawing.Size(0,0)
$HashForGui.MainWebBrowser.ScrollBarsEnabled=$True
$HashForGui.MainForm.Controls.Add($HashForGui.MainWebBrowser)
#---WebBrowser info section load.
$HashForGui.MainWebBrowser.DocumentText=get-content $HtmlLoadingFilePath
$HashForGui.MainForm.ShowDialog()
exit
Timer thread
Remove-ItemProperty -Path "Registry::$ProductRegistryPath" -Name "Time Out" -Force -ErrorAction SilentlyContinue
[Int32]$Timer=0
do {
start-sleep -s 1
$Timer=$Timer+1
if ($Debug -eq 1) {write-host -Foreground Blue "Timer : $Timer/$MaxTimer"}
} until ($Timer -ge $MaxTimer)
New-ItemProperty -Path "Registry::$ProductRegistryPath" -Name "Time Out" -PropertyType String -value "1" -Force
exit

Related

Azure Logic App - How can I re-started my operations failed?

I have many operations failed in my Azure Logic App.
I see that if you click on a single operation on the Azure portal you can re-started the operation:
Is it possible to select ALL of these failed operations, and re-run all together?
Thanks a lot guys
If you want to resubmit one or more logic app runs that failed, succeeded, or are still running, you could bulk resubmit Logic Apps from the Runs Dashboard.
About how to use this function, you could refer to this article:Monitor logic apps with Azure Monitor logs. Under the tile View logic app run information, you could find the Resubmit description.
Alternative to bulk resubmit Logic Apps from the Runs Dashboard, you can utilize the PowerShell commands. Take a look at the script below, which can automate listing Failed logic app runs, identify triggers, actions responsible and restart the apps by input ResourceGroupName. You can change some of those bits as per your needs. (skip the interactions and just restart apps again) I have just show it for understanding.
Using: Get-AzLogicApp, Get-AzLogicAppRunHistory ,
Get-AzLogicAppRunAction, Get-AzLogicAppTrigger and Start-AzLogicApp
cmdlets.
Script using Az PowerShell 6.2 Module: Az.LogicApp [Copy below to file, say restart.ps1 and run] Make sure you assign $rg with your actual AzResourceGroup name
$rg = "MyResourceGrp"
#get logic apps
$logicapps = Get-AzLogicApp -ResourceGroupName $rg
Write-Host "logicapps:" -ForegroundColor "Yellow"
write-output $logicapps.Name
#list all logicapp runs failed
$failedruns = #(foreach($name in $logicapps.Name){
Get-AzLogicAppRunHistory -ResourceGroupName $rg -Name $name | Where {$_.Status -eq 'Failed'}
})
Write-Host "failedruns:" -ForegroundColor "Yellow"
Write-Output $failedruns.Name | select -Unique
Write-Host "failedruns: LogicAppNames" -ForegroundColor "Yellow"
Write-Output $failedruns.Workflow.Name | select -Unique
#list all logicappRunsActions failed
foreach($i in $logicapps){
foreach($y in $failedruns){
if ($i.Version -eq $y.Workflow.Name) {
$resultsB = Get-AzLogicAppRunAction -ResourceGroupName $rg -Name $i.Name -RunName $y.Name -FollowNextPageLink | Where {$_.Status -eq 'Failed'}
}
}
}
foreach($item in $resultsB){
Write-Host "Action:" $item.Name " " -NoNewline "Time:" $item.EndTime
Write-Output " "
}
#get logicapp triggers
foreach($ii in $logicapps){
foreach($yy in $failedruns){
if ($ii.Version -eq $yy.Workflow.Name) {
$triggers = Get-AzLogicAppTrigger -ResourceGroupName $rg -Name $ii.Name
}
}
}
Write-Host "triggers:" -ForegroundColor "Yellow"
Write-Output $triggers.Name
#start logic apps with triggers
Write-Host "Starting logic apps....." -ForegroundColor "green"
foreach($p in $logicapps){
foreach($tri in $triggers){
if ($p.Version -eq $triggers.Workflow.Name) {
Start-AzLogicApp -ResourceGroupName $rg -Name $p.Name -TriggerName $tri.Name
}
}
}
$verify = Read-Host "Verify ruuning app? y or n"
if ($verify -eq 'y') {
$running = #(foreach($name2 in $logicapps.Name){
Get-AzLogicAppRunHistory -ResourceGroupName $rg -Name $name2 | Where {$_.Status -eq 'Running' -or $_.Status -eq 'Waiting'}
})
Write-Host $running
}
else {
Write-Host "Bye!"
}
Although my LogicApp has failed again, but you can see it was triggered in time by script
Note: If your logic app trigger expects inputs or actions (unlike recurrence or scheduled) please edit or make changes accordingly for Start-AzLogicApp command to execute successfully.
Here I am considering all logic apps are enabled (use -State Enabled) parameter for Get-AzLogicApp command if you want to run this on only currently enabled apps.
Example: Get-AzLogicApp -ResourceGroupName "rg" | where {$_.State -eq 'Enabled'}
2. You can also try advanced settings for triggers in workflow. Such as retry policy.
You can specify it to retry at custom intervals in case of failures due to an intermittent issues.
You can submit a feedback or Upvote a similar Feedback : ability-to-continue-from-a-particular-point-as-in
Refer: help topics for the Logic Apps cmdlets

How to use FileInfo objects when multithreading

Im having trouble using deserialized file info object when running multiple threads
Main gets file info and starts a new thread. The thread will load my module file and "use" the file that I got in main
$file = Get-FileInfo -path "test.zip"
$Scriptblock = {
Import-Module ".\path\to\foo\module.psm1"
Foo -File $using:file
}
Start-Job -ScriptBlock $ScriptBlock
In my thread, Im not able to call $file.Basename because my object is deseralized when passing it into my thread. The Deseralized.IO.fileinfo class has less functionality than the original FileInfo class
I want to be able to have the same functionality with $file in my thread as I do in main
Is there a way for me to do this? Perhaps by reserializing my object..?
I think you'd be better served by passing the file into the scriptblock as a parameter and then calling Get-FileInfo inside the job:
$MyPath = "test.zip"
$Scriptblock = {
Import-Module ".\path\to\foo\module.psm1"
Foo -File $using:file
$file = Get-FileInfo -path $Using:MyPath
}
Start-Job -ScriptBlock $ScriptBlock
Alternative:
$MyPath = "test.zip"
$Scriptblock = {
param([String]$Path)
Import-Module ".\path\to\foo\module.psm1"
Foo -File $using:file
$file = Get-FileInfo -path $Path
}
Start-Job -ScriptBlock $ScriptBlock -ArgumentList $MyPath

Stop multiple Azure VMs at the same time using Azure PowerShell

Get-AzureRmVM -ResourceGroupName RG-VNETS |
ForEach-Object {
Get-AzureRmVM -ResourceGroupName RG-VNETS -Name $_.Name -Status
} |
ForEach-Object {
if (-Not ($_.Statuses[1].DisplayStatus -like "*deallocated*")) {
Stop-AzureRmVM -ResourceGroupName RG-VNETS -Name $_.Name -Force
}
}
I've got this script that stops all my Azure VMs, the catch here is that this script shuts down one VM at a time.
i.e. if I have three VMs: VM1, VM2, VM3
The script doesn't shut down VM2 until VM1 is fully shutdown and so on. I don't know if there's a way to tell PowerShell not to wait for each VM to be fully shutdown to proceed with the following one.
There is already a feature request on GitHub for doing such operations asynchronously which should be implemented in the near future.
In the meantime you could do a workaround like the following using the PoshRSJob module - just replace temp4so with your resource group name
# Install PoshRSJob if necessary
#
# Install-Module PoshRSJob
Login-AzureRmAccount
$start = Get-Date
$jobs = Get-AzureRmVM -ResourceGroupName temp4so |
% {
Get-AzureRmVM -ResourceGroupName temp4so -Name $_.Name -Status
} |
% {
if (-Not ($_.Statuses[1].DisplayStatus -like "*deallocated*")) {
$vm = $_
Start-RSJob {
Stop-AzureRmVM -ResourceGroupName temp4so -Name ($using:vm).Name -Force
}
}
}
$jobs | Wait-RSJob | Receive-RSJob
$jobs | Remove-RSJob
$end = Get-Date
Write-Host ("Stopping took {0}" -f ($end - $start))
which in my test case with 3 VMs resulted in output similar to the following which shows that the operations where done in parallel
OperationId :
Status : Succeeded
StartTime : 24.09.2016 18:49:10
EndTime : 24.09.2016 18:51:32
Error :
OperationId :
Status : Succeeded
StartTime : 24.09.2016 18:49:11
EndTime : 24.09.2016 18:51:22
Error :
OperationId :
Status : Succeeded
StartTime : 24.09.2016 18:49:11
EndTime : 24.09.2016 18:51:22
Error :
Stopping took 00:02:32.9115538
Note: You cannot simply use the standard Start-Job to offload the sync. operations to a background job as the newly created PowerShell instances in the background do not share the context with your initial session and therefore would require you to authenticate again for each of those sessions. As PoshRSJob uses PowerShell runspaces within the initial PowerShell instance it does not require to authenticate again.
They implemented -AsJob per the GitHub feature request DAXaholic linked. Here's an example taken from the GitHub comments section.
$VMList = Get-AzureRmVM -ResourceGroupName $resourceGroupName
$JobList = #()
foreach ($vm in $VMList) {
$JobList += Stop-AzureRmVm -ResourceGroupName $resourceGroupName -Name $vm -Force -AsJob | Add-Member -MemberType NoteProperty -Name VMName -Value $vm.Name -PassThru
}
and if you want to wait for the jobs to finish with cleanup:
$JobList | Wait-Job | Receive-Job
$JobList | Remove-Job

Enabling Sharepoint Features With Powershell

I am new to PowerShell and have been asked to modify a script that is used to activate features on a site. The script has some latency issues between the two feature activations, so I decided to make a new function that will enable the feature and sleep or delay until the feature is finished enabling. Will this work? And if so, how can I tell that the feature is finished activating?
# This is a function that enable's a Sharepoint Feature and Sleeps Until Its Finished Enabling
function Feature-Enable
{param ([string]$ID, [string]$URL)
#Enable Feature
Enable-SPFeature -Identity $ID -url $URL -Confirm:$false
#Sleep Until Feature is Done Enabling
}
#Set parameters/variables for script
$serverName = "someServerName"
$rootwebUrl = "someRootUrl"
$currentpath=Split-Path -Path $MyInvocation.MyCommand.Path -Parent
$siteURL = "http://" + $servername + $rootwebURL
$start = Get-Date -DisplayHint Time
# check to ensure Microsoft.SharePoint.PowerShell is loaded
$snapin = Get-PSSnapin | Where-Object {$_.Name -eq 'Microsoft.SharePoint.Powershell'}
if ($snapin -eq $null) {
Write-Host "Loading SharePoint Powershell Snapin"
Add-PSSnapin "Microsoft.SharePoint.Powershell"
}
# Active Site Collection Features (in order)
write-host ""
write-host "----------------------------------------"
write-host "Begin activation of site collection features"
write-host "----------------------------------------"
write-host ""
Feature-Enable –identity "3cbf5d1e-ff2e-48d2-82a4-99b060381268" -URL $siteUrl
#NOTE: POTENTIAL LATENCY ISSUES. MAY NEED TO INSERT DELAY TIMER!
Feature-Enable –identity "bbde524e-9293-468e-84f7-fdb763ffa309" -URL $siteUrl
write-host ""
write-host "----------------------------------------"
write-host "Activation of site collection features - DONE!"
write-host "----------------------------------------"
write-host ""
$end= Get-Date -DisplayHint Time
Write-Host "Started at: " $start " Ended at: " $end;
function WaitForJobToFinish([string]$SolutionFileName)
{
$JobName = "*solution-deployment*$SolutionFileName*"
$job = Get-SPTimerJob | ?{ $_.Name -like $JobName }
if ($job -eq $null)
{
Write-Host 'Timer job not found'
}
else
{
$JobFullName = $job.Name
Write-Host -NoNewLine "Waiting to finish job $JobFullName"
while ((Get-SPTimerJob $JobFullName) -ne $null)
{
Write-Host -NoNewLine .
Start-Sleep -Seconds 2
}
Write-Host "Finished waiting for job.."
}
}

PowerShell: Runspace problem with DownloadFileAsync

I needed to download file using WebClient in PowerShell 2.0, and I wanted to show download progress, so I did it this way:
$activity = "Downloading"
$client = New-Object System.Net.WebClient
$urlAsUri = New-Object System.Uri($url)
$event = New-Object System.Threading.ManualResetEvent($false)
$downloadProgress = [System.Net.DownloadProgressChangedEventHandler] {
$progress = [int]((100.0 * $_.BytesReceived) / $_.TotalBytesToReceive)
Write-Progress -Activity $activity -Status "${progress}% done" -PercentComplete $progress
}
$downloadComplete = [System.ComponentModel.AsyncCompletedEventHandler] {
Write-Progress -Activity $activity -Completed
$event.Set()
}
$client.add_DownloadFileCompleted($downloadComplete)
$client.add_DownloadProgressChanged($downloadProgress)
Write-Progress -Activity $activity -Status "0% done" -PercentComplete 0
$client.DownloadFileAsync($urlAsUri, $file)
$event.WaitOne()
I am getting a error There is no Runspace available to run scripts in this thread. for the code in $downloadProgress handler, which is logical. However, how do I provide a Runspace for the thread that (probably) belongs to the ThreadPool?
UPDATE:
Note that both answers to this question are worth reading, and I would accept both if I could.
Thanks stej for the nod.
Andrey, powershell has its own threadpool and each service thread keeps a threadstatic pointer to a runspace (the System.Management.Automation.Runspaces.Runspace.DefaultRunspace static member exposes this - and would be a null ref in your callbacks.) Ultimately this means it's difficult - especially in script - to use your own threadpool (as is provided by .NET for async methods) to execute scriptblocks.
PowerShell 2.0
Regardless, there is no need to play with this as powershell v2 has full support for eventing:
$client = New-Object System.Net.WebClient
$url = [uri]"http://download.microsoft.com/download/6/2/F/" +
"62F70029-A592-4158-BB51-E102812CBD4F/IE9-Windows7-x64-enu.exe"
try {
Register-ObjectEvent $client DownloadProgressChanged -action {
Write-Progress -Activity "Downloading" -Status `
("{0} of {1}" -f $eventargs.BytesReceived, $eventargs.TotalBytesToReceive) `
-PercentComplete $eventargs.ProgressPercentage
}
Register-ObjectEvent $client DownloadFileCompleted -SourceIdentifier Finished
$file = "c:\temp\ie9-beta.exe"
$client.DownloadFileAsync($url, $file)
# optionally wait, but you can break out and it will still write progress
Wait-Event -SourceIdentifier Finished
} finally {
$client.dispose()
}
PowerShell v1.0
If you're stuck on v1 (this is not specifically for you as you mention v2 in the question) you can use my powershell 1.0 eventing snap-in at http://pseventing.codeplex.com/
Async Callbacks
Another tricky area in .NET is async callbacks. There is nothing directly in v1 or v2 of powershell that can help you here, but you can convert an async callback to an event with some simple plumbing and then deal with that event using regular eventing. I posted a script for this (New-ScriptBlockCallback) at http://poshcode.org/1382
Hope this helps,
-Oisin
I see that you use async call so that you can show the progress. Then you can use BitsTransfer module for that. It shows the progress by default:
Import-Module BitsTransfer
Start-BitsTransfer -Source $url -dest d:\temp\yourfile.zip
If you would like to transfer the file in the background, you could use something like this:
Import-Module BitsTransfer
$timer = New-Object Timers.Timer
$timer.Interval = 300
Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action {
if ($transfer.JobState -ne 'Transferring') {
$timer.Enabled = 0;
Write-Progress -Completed -Activity Downloading -Status done
return
}
$progress = [int](100* $transfer.BytesTransferred/$transfer.BytesTotal)
Write-Progress -Activity Downloading -Status "$progress% done" -PercentComplete $progress
} -sourceId mytransfer
$transfer = Start-BitsTransfer -Source $url -dest d:\temp\yourfile.zip -async
$timer.Enabled = 1
# after that
Unregister-Event -SourceIdentifier mytransfer
$timer.Dispose()
The key parameter is -async. It starts the transfer in background. I haven't found any event triggered by the transfer, so I query the job each second to report the state via Timers.Timer object.
However with this solution, it is needed to unregister the event and dispose the timer. Some time ago I had problems with unregistering in the scriptblock passed as -Action (it could be in the if branch), so I unregister the event in separate command.
I think #oising (x0n) has some solution on his blog. He'll tell you that hopefully and that would be answer for your question.

Resources