In the code blocks below, I'm trying to run Get-MyCmdlet in 3 separate threads, each of which opens a google page if the Get-MyCmdlet did not give anything.
The Get-MyCmdlet is fairly simple, the only thing it does is WriteObject("hello world"); (defined in the c# code).
However the script always opens up a google page, unless I change the Get-MyCmdlet to Get-Host(which is a system default cmdlet).
Is it because the custom cmdlets are not supporting multi-threading? Any help will be greatly appreciated!
The cmdlet:
[Cmdlet(VerbsCommon.Get, "MyCmdlet ")]
public class GetMyCmdlet : Cmdlet
{
protected override void ProcessRecord()
{
WriteObject("hello world");
}
}
the script:
$ScriptBlock = {
$result = Get-MyCmdlet
if (!$result) {[System.Diagnostics.Process]::Start("http://www.google.com")}
$name = ($result | get-member)[-1].name
$result = $result.$name
return $result
}
....
$threads = 3
for ($i = 0; $i -lt $threads) {
$running = #($jobs | Where-Object {$_.State -match 'Running'})
Write-Host $running.length
if ($running.length -lt $threads) {
$jobs += Start-job -ScriptBlock $ScriptBlock
$i = $i + 1
} else {
get-job | receive-job
$finished = #($jobs | Where-Object ($_.State -match 'Completed'))
if ($finished) {
$finished.getType()
foreach($job in $finished) {
Receive-Job -keep $job | Out-File "Output$.txt"
$i = $i + 1
$finished.Remove($job)
Remove-Job -job $job
}
}
}
}
No custom cmdlets are not the problem. My guess is the problem is that calling Get-MyCmdlet fails with an error so $result is not set and thanks to your if the browser is started. If you would check the results of your jobs you would see an error message. You probably need to make sure the job initializes right so you can call your cmdlet. You could use the -InitializationScript parameter of Start-Job to import your module for the job. See here, here and here for more information.
Related
I'm trying to loop through an array of servers and run this test path command as a background thread. It needs to be a thread because I will be monitoring it for a time out and killing the thread if it exceeds that time out.
I cannot get a variable return value, nor do I know how I would have separate variables for multiple machines or if I'm passing the variables properly. In c# I would use a thread collection, but I can't seem to figure out the syntax for Powershell.
foreach ($server in $servers)
{
Write-Host $server;
$scriptBlock =
{
$returnVal = Test-Path "\\$server\c$";
return $returnVal;
}
$remoteReturnVal = Invoke-Command -ComputerName [Environment]::MachineName -ScriptBlock { $script } –AsJob;
}
Here is how I can do it in Powershell with c# (the below works)
$shareCheck =
#'
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace powershellConsoleForm
{
public class shares
{
public List<string> shareExists(List<string> servers, int timeOut)
{
List<string> returnValue = new List<string>();
foreach (string server in servers)
{
string path = #"\\" + server + #"\c$";
Func<bool> func = () => Directory.Exists(path);
Task<bool> task = new Task<bool>(func);
task.Start();
if (task.Wait(timeOut))
{
//return task.Value;
Console.Write(" ");
Console.Write("success " + task.Result);
Console.Write(" ");
returnValue.Add(server + "|" + task.Result.ToString());
}
else
{
Console.Write("timeout");
returnValue.Add(server + "|timeout");
}
}
Console.Write("Done");
return returnValue;
}
}
}
'#
try
{
$shareChecker = New-Object powershellConsoleForm.shares;
}
catch
{
$assemblies = ("System", "System.Collections", "System.ComponentModel", "System.Data", "System.Drawing", "System.IO", "System.Linq", "System.Management.Automation", "System.Security", "System.Threading.Tasks", "System.Windows.Forms", "System.Threading", "System.Collections.Concurrent", "System.Security.Principal");
Add-Type -TypeDefinition $shareCheck -ReferencedAssemblies $assemblies -Language CSharp
$shareChecker = New-Object powershellConsoleForm.shares;
}
$servers = #("server1", "server2", "server3", "server4", "server5");
$timeOut = 11000;
[int] $counter = 0;
do
{
$counter ++;
Write-Host $counter;
$shareAvailibilities = $shareChecker.shareExists($servers, $timeOut);
foreach ($shareAvailibility in $shareAvailibilities)
{
Write-Host $shareAvailibility;
}
Write-Host " ";
Write-Host " ";
Sleep 5;
}while ($true)
First thing, Invoke-Command by defaults returns the output of the scriptblock. So your scriptblock could be something like $ScriptBlock = {Test-Path "\\$server\c$"}.
Then, there are several issues with your Invoke-Command line.
Since you're using -AsJob, you don't need to assign output to a variable. You would access the results of your job with Get-Job.
Then you could add -JobName $server so it's easier to view the results by server.
Remove -ComputerName as it runs on localhost by default. As a side note, I personally like using $env:COMPUTERNAME instead as it's a bit easier on the eyes, and to access.
You have wrapped your $script variable inside of a scriptblock, when it's already a script block. Remove the curly braces. But, since the command is so simple, it might make more sense to just keep the braces, and replace variable with the command.
here's a revised copy of your script, based on my adjustments (and a couple formatting tweaks):
foreach ($server in $servers) {
Write-Host $server
Invoke-Command -ScriptBlock { Test-Path "\\$server\c$" } –AsJob -JobName $server
}
Get-Job
I was thinking about it, and since you're just running these on the local machine, it would make a lot more sense to just use Start-Job:
foreach ($server in $servers) {
Write-Host $server
Start-Job -Name $server -ScriptBlock { Test-Path "\\$server\c$" }
}
Get-Job
Then to get the results of the jobs themselves you can use Receive-Job. Either pipe Get-Job to Receive-Job, or access each one by name Receive-Job -Name <servername>
I have a complex PowerShell function which I would like to run another threads.
However, If I'm right, the function cannot be accessed in the scriptblock. I want to avoid to copy every related function next to it.
Is there any way or method to call the function in the scriptblock?
function Function1 {
Param()
Process {
$param1 = "something"
$pool = [RunspaceFactory]::CreateRunspacePool(1, [int]$env:NUMBER_OF_PROCESSORS + 1)
$pool.ApartmentState = "MTA"
$pool.Open()
$runspaces = #()
$scriptblock = {
Param (
[Object] [Parameter(Mandatory = $true)] $param1
)
Complex_Function -param1 $param1
}
1..10 | ForEach-Object {
$runspace = [PowerShell]::Create()
$null = $runspace.AddScript($scriptblock)
$null = $runspace.AddArgument($param1)
$runspace.RunspacePool = $pool
$runspaces += [PSCustomObject]#{ Pipe = $runspace; Status = $runspace.BeginInvoke() }
}
while ($runspaces.Status -ne $null) {
$completed = $runspaces | Where-Object { $_.Status.IsCompleted -eq $true }
foreach ($runspace in $completed) {
$runspace.Pipe.EndInvoke($runspace.Status)
$runspace.Status = $null
}
}
$pool.Close()
$pool.Dispose()
}
}
function Complex_Function {
Param(
[Object] [Parameter(Mandatory = $true)] $param1
)
Process {
#several function calls
}
}
I think the code found in this blog post is probably what you're looking for:
Function ConvertTo-Hex {
Param([int]$Number)
'0x{0:x}' -f $Number
}
#Get body of function
$Definition = Get-Content Function:\ConvertTo-Hex -ErrorAction Stop
#Create a sessionstate function entry
$SessionStateFunction = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry
-ArgumentList 'ConvertTo-Hex', $Definition
#Create a SessionStateFunction
$InitialSessionState.Commands.Add($SessionStateFunction)
#Create the runspacepool by adding the sessionstate with the custom function
$RunspacePool = [runspacefactory]::CreateRunspacePool(1,5,$InitialSessionState,$Host)
Do something similar with your Complex_Function and (I guess) every other function you need and they should be usable by your runspaces.
edit
You asked in comments how to gather all functions. The path function:/ can be traversed and searched like a directory, so get-chiditem function:/ gets all currently-defined functions.
In experimenting with this, it seems as though functions that are defined within the current script, or from dot-sourced scripts, have an empty Source property. Play around with this. It should lead to what you want.
$InitialSessionState = [initialsessionstate]::Create()
Get-ChildItem function:/ | Where-Object Source -like "" | ForEach-Object {
$functionDefinition = Get-Content "Function:\$($_.Name)"
$sessionStateFunction = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry `
-ArgumentList $_.Name, $functionDefinition
$InitialSessionState.Commands.Add($sessionStateFunction)
}
Function A{
...}
Function B{
...}
Function C{
...}
$Servers = "Server1","Server2","Server3",...
ForEach($server in $servers){
$Session = New-PSSession $server
Invoke-Command -ScriptBlock ${Function:A} -Session $Session
Invoke-Command -ScriptBlock ${Function:B} -Session $Session
Invoke-Command -ScriptBlock ${Function:C} -Session $Session
}
I am running a script with functions and invoking them on a jumpoint to several servers.
In this case, Invoke-command works sequentially, invoking function A then B then C onto the servers.
How can I invoke them in parallel?
Thanks
use server array directly in invoke-command
$servers = 'server1','server2','server3','server4'
$cred = Get-Credential
function a {
echo "Function a $env:COMPUTERNAME"
}
function b {
echo "Function b $env:COMPUTERNAME"
}
function c {
echo "Function c $env:COMPUTERNAME"
}
# Put function definition to import in script block
$allFunctionDefs = "
function a { ${function:a} };
function b { ${function:b} };
function c { ${function:c} }
"
# Run function
Invoke-Command -ComputerName $servers -ScriptBlock {
Param( $allFunctionDefs )
. ([ScriptBlock]::Create($allFunctionDefs))
a
b
c
} -Credential $cred -ArgumentList $allFunctionDefs
I have a custom dll, and right now in my multi-thread codes, I am referencing this dll by:
....
$threads = 6
for ($i2 = 0; $i2 -lt $threads)
{
Copy-Item myCmdlet.dll $i2.dll
$i2++
}
for ($i = 0; $i -lt $threads)
{
$jobs += Start-job -ScriptBlock $ScriptBlock -ArgumentList ($i)
}
....
$ScriptBlock = {
param($i)
Installutil $i.dll
Get-PSSnapIn -Registered
Add-PSSnapIn MyCmdletSet
$result = Get-MyCmdlet
....
}
....
This would be annoying because I may end up with many copies of the original dll. Is it any other way to call a dll in multi-threading mechanism?
Thanks for all answers!
If you're on PowerShell v2 or higher, you don't need to install/register snapins anymore. Just use Import-Module -Path <path-to-dll>. So your scripblock would look like:
$ScriptBlock = {
param($i)
Import-Module c:\somepath\mycmdlet.dll
$result = Get-MyCmdlet
....
}
I have this code below and I would like to display the output in a table like fashion
$spSite = Get-SPSite "http://sp2010"
$spWeb = $spSite.OpenWeb()
$spList = $spWeb.Lists["ReportList"]
$items = $spList.Items
foreach($item in $items)
{
Write-Host $item["ID"] $item["Name"] $item["Department"] $item["Telephone"]
}
like this:
ID Name Department Telephone
--- -------- ----------- ------------
1 Somone IT 02554445588
I saw Format-Table, but I cannot seem to get the hang of it.
Some example using the code above will be very much appreciated.
Thanks a lot.
Hard to even start helping, because current code simply won't work... ($spListItem is... what? foreach uses $item, can we assume you are using it?). Also: we have very little to no information about type of this items, it looks like some dictionary object... Anyway, I would try:
$items | ForEach-Object {
# assume $items is array of dictionary objects...
New-Object PSObject -Property $_
} | Format-Table ID, Name, Department, Telephone
obviously, it may not work, because I made some assumptions that can be just wrong.. ;)
Give this a try:
function Get-SPListItem
{
param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[Microsoft.SharePoint.SPList]$List,
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$ViewName
)
process
{
try
{
if($PSCmdlet.MyInvocation.BoundParameters.ContainsKey('ViewName'))
{
$views = $list.Views | Select-Object -ExpandProperty Title
if($views -contains $ViewName)
{
$columns = $List.Views[$ViewName].ViewFields
}
else
{
throw 'Invalid view name'
}
}
else
{
$columns = $List.DefaultView.ViewFields
}
foreach($item in $List.Items)
{
$obj = New-Object PSObject
foreach($col in $columns)
{
$obj | Add-Member -MemberType NoteProperty -Name $col -Value $item[$col]
}
$obj
}
}
catch
{
Write-Error $_
}
}
}
$spWeb.Lists["ReportList"] | Get-SPListItem