Powershell parallel set-variable - powershell

I have a script that functions the way I want it to but it's slow. I tried using the same method in a workflow with foreach parallel but the set-variable command is not something that can be used within a workflow. I wanted to see if the way I'm doing this is incorrect and if there's a better way to get what I'm doing. The reason I want to do parallel requests is because the script can take quite a long time to complete when expanded to 20+ servers as is does each server in turn where as being able to do them all in one go would be quicker.
Below is a dumbed down version of the script (that works without parallel foreach) but it's effectively what I need to get working:
$servers = #("server1", "server2");
foreach ($s in $servers) {
$counter_value = get-counter "\\$s\counter_name"
Set-Variable -name "{s}counter" -value $counter_value
write-host ${server1counter}

Commands not supported in workflows needs to be executed in an Inlinescript. Try (untested):
workflow t {
$servers = #("server1", "server2");
foreach -parallel ($s in $servers) {
inlinescript {
$counter_value = get-counter "\\$using:s\counter_name"
Set-Variable -name "$($using:s)counter" -value $counter_value
#write-host with a PerformanceCounterSampleSet isn't a good combination. You'll only get the typename since it's a complex type (multiple properties etc.)
write-host (Get-Variable "$($using:s)counter" -ValueOnly)
}
}
}
t

Related

syntax in for-loop using local variables

Many of my scripts are using PSsession quite often checking on available shares on several remote PCs. PS-Remoting is great.
Below is the simple working code-snippet:
if (Invoke-Command -Session $PSess -ScriptBlock {Get-SMBShare -Name $using:shareName_1 -ea 0}) {
$mapPS_1=0
write-Host "available: $shareName_1"
} else {
$mapPS_1=1
write-Host "NOT available: $shareName_1"
}
Instead of repeating this code several times (only with differing variables for the shareNames according to the examples in the list) I would like to create a for-loop (possibly) containing just that code.
Here is a list of shares to be checked upon:
$shareName_1='TC-MEDIA-SYSTEM'
$shareName_2='TC-MEDIA-DATA1'
$shareName_3='V-15TB_01'
$shareName_4='W-LL-503DD'
$shareName_5='X-TL-503AA'
$shareName_6='Y-LL-503BB'
$shareName_7='Z-LL-503CC'
$shareName_8='DUMPSTER_01'
$shareName_9='BKUP-NAS-01'
$shareName_10='TC-DUMPSTER_02'
As one can read from the code-snippet, depending on the resulting output, one local variable $mapPS gets manipulated.
All this works fine as shown above.
The trouble I am facing is to find the proper syntax for this to run in a for-loop.
I'd assume that the variable $using:shareName_$i is the troubling factor.
This is what I've got so far. Unfortunately, not bearing any fruit.
for ($i = 1; $i -lt 11; $i++) {
if (Invoke-Command -Session $PSess -ScriptBlock {Get-SMBShare -Name $using:(Get-Variable -Name shareName_$i).Value} -ea 0) {
Set-Variable -Name mapPS_$i value 0
write-Host "available: $shareName_$i"
} else {
Set-Variable -Name mapPS_$i value 1
write-Host "NOT available: $shareName_$i"
}
}
I'm sure Powershell has a solution for this - I just don't know it.
Any hints leading to the solution are appreciated.
Generally, consider storing your share names in an array instead of in individual variables (e.g., $shareNames ='TC-MEDIA-SYSTEM', 'TC-MEDIA-DATA1', ...), which allows you to reference them by index (e.g., $shareNames[0]) instead of having to resort to variable indirection via the Get-Variable cmdlet.
A simple solution is to create an aux. local variable in the loop that your remote script can reference via the $using: scope (which doesn't support commands or expressions):
foreach ($i in 1..10) {
$shareName = Get-Variable -ValueOnly "$shareName_$i"
if (Invoke-Command -Session $PSess -ScriptBlock { Get-SMBShare -Name $using:shareName } -ea 0) {
Set-Variable -Name mapPS_$i value 0
write-Host "available: $shareName_$i"
}
else {
Set-Variable -Name mapPS_$i value 1
write-Host "NOT available: $shareName_$i"
}
}

How do you pass values to another script under a foreach statement?

I want to run 1 script multiple times for each port select via a range and pass through the port details in which it needs to use to connect, I was trying to use the following:
$availableports = 7000..7050
while ($availableports -notcontains $SPort) {
[string]$SPort= Read-Host -Prompt 'S Ports'
}
while ($availableports -notcontains $FPort) {
[string]$FPort= Read-Host -Prompt 'F Ports'
}
$massport = ($SPort)..($FPort)
foreach ($Port in $massport) {
C:\PShell-Projects\Firmware\SCP-FMUPv2.ps1 -Port "$Port"
}
This works but will not move on to the next port until the referenced script has finished.
I would like to run them all in parallel.
I tried
$arg = #("-Port", $portm)
Start-Job -FilePath C:\PShell-Projects\Firmware\SCP-FMUPv2.ps1 -ArgumentList $arg
but the job becomes blocked and when I used Receive-Job it asks for the port to open a connection to, which should have been sent as part of the loop.
I seem to be missing some key information, but don't know where to start and well when looking up the information nothing seems to be standing out.
This works for me:
$jobs = #()
foreach ($Port in $massport) {
$jobs += start-job -FilePath "job.ps1" -ArgumentList #($port)
}
Receive-Job $jobs -Wait
(no named arguments in ArgumentList).
You can also take a look at Invoke-Parallel function, which simplifies running parallel tasks.
You could probably use -asjob after your C:\PShell-Projects\Firmware\SCP-FMUPv2.ps1 -Port "$Port command to have it just do them all simultaneously.
If the ps1 won't take -asjob, you might be able to wrap it with invoke-command {}

Better way to query remote server registries?

I've built this small block of code to query and store the values of a group of servers, which seems to work fine, however I'd like to know if there is a "pure PowerShell" way to do this.
$eServers = Get-ExchangeServer
$Servers = $eServers | ?{$_.Name -like "Delimit_server_group"}
foreach ($server in $Servers)
{
[string]$Key1 = "\\$server\HKLM\SYSTEM\CurrentControlSet\Control\"
[string]$rKeys += (REG QUERY "$key1" /s)
}
You can use the RegistryKey class to open a remote registry:
$RemoteHKLM = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$server)
$RemoteKey = $RemoteHKLM.OpenSubKey('SYSTEM\CurrentControlSet\Control')
# Following will return all subkey names
$RemoteKey.GetSubKeyNames()
You'll have to implement recursive traversal yourself if you need functionality equivalent to reg query /s
Matthias' answer is probably your best option, but there are other approaches you could take as well. If you have PSRemoting enabled on your systems, you could for instance invoke remote commands like this:
$key = 'HKLM:\SYSTEM\CurrentControlSet\Control'
Invoke-Command -Computer $Servers -ScriptBlock {
Get-ChildItem $args[0] | Select-Object -Expand Name
} -ArgumentList $key

Run functions in parallel

Is there any way to run parallel programmed functions in PowerShell?
Something like:
Function BuildParallel($configuration)
{
$buildJob = {
param($configuration)
Write-Host "Building with configuration $configuration."
RunBuilder $configuration;
}
$unitJob = {
param()
Write-Host "Running unit."
RunUnitTests;
}
Start-Job $buildJob -ArgumentList $configuration
Start-Job $unitJob
While (Get-Job -State "Running")
{
Start-Sleep 1
}
Get-Job | Receive-Job
Get-Job | Remove-Job
}
Does not work because it complains about not recognizing "RunUnitTests" and "RunBuilder", which are functions declared in the same script file. Apparently this happens because the script block is a new context and does not know anything about the scripts declared in the same file.
I could try to use -InitializationScript in Start-Job, but both RunUnitTests and RunBuilder call more functions declared in the same file or referred from other files, so...
I'm sure there's a way to do this, since it's just modular programming (functions, routines and all that stuff).
You could have the functions in a separate file and import them into the current context wherever needed via dot sourcing. I do this in my Powershell profile, so some of my custom functions are available.
$items = Get-ChildItem "$PSprofilePath\functions"
$items | ForEach-Object {
. $_.FullName
}
If you wanted import one file, it would just be:
. C:\some\path\RunUnitTests.ps1

What's the fastest way to get online computers

I'm writing a function which returns all Online Computers in our network, so I can do stuff like this:
Get-OnlineComputers | % { get-process -computername $_ }
Now I basically got my function ready, but it's taking way too long.
I want to only return Computers which have WinRM active, but I also want to provide the option to get every computer even those which haven't got WinRM set up (switch parameter).
This is my function. first it creates a pssession to the domaincontroller, to get all computers in our LAN. then foreach computer, it will test if they have WinRM active or if they accept ping. if so, it gets returned.
$session = New-PSSession Domaincontroller
$computers = Invoke-Command -Session $session { Get-ADComputer -filter * } | select -ExpandProperty Name
$computers | % {
if ($IncludeNoWinRM.IsPresent)
{
$ErrorActionPreference = "SilentlyContinue"
$ping = Test-NetConnection $_
if ($ping.PingSucceeded -eq 'True')
{
$_
}
}
else
{
$ErrorActionPreference = "SilentlyContinue"
$WinRM = Test-WSMan $_
if ($WinRM)
{
$_
}
}
}
Is this the best way I can go to check my online computers? Does anyone have a faster and better idea?
Thanks!
Very Quick Solution is using the -Quiet Parameter of the Test-Connection cmdlet:
so for example:
$ping = Test-Connection "Computer" -Quiet -Count 1
if ($ping)
{
"Online"
}
else
{
"Offline"
}
if it's not enough fast for you, you can use the Send Method of the System.Net.NetworkInformation.Ping
here's a sample function:
Function Test-Ping
{
Param($computer = "127.0.0.1")
$ping = new-object System.Net.NetworkInformation.Ping
Try
{
[void]$ping.send($computer,1)
$Online = $true
}
Catch
{
$Online = $False
}
Return $Online
}
Regarding execute it on multiple computers, I suggest using RunSpaces, as it's the fastest Multithreading you can get with PowerShell,
For more information see:
Runspaces vs Jobs
Basic Runspaces implemenation
Boe Prox (master of runspaces) has written a function which is available from the Powershell Gallery. I've linked the script below.
He uses many of the answers already given to achieve the simultaneous examination of 100s of computers by name. The script gets WMI network information if test-connection succeeds. It should be fairly easy to adapt to get any other information you want, or just return the result of the test-connection.
The script actually uses runspace pools rather than straight runspaces to limit the amount of simultaneous threads that your loop can spawn.
Boe also wrote the PoSH-RSJob module already referenced. This script will achieve what you want in native PoSH without having to install his module.
https://gallery.technet.microsoft.com/scriptcenter/Speedy-Network-Information-5b1406fb