Unit Testing with Pester - powershell

I understand the Theories but need a little help in practice when it comes to testing.
So I'm still in the process of learning testing in general and after some research I can see that the code coverage is generated by how much each line of code is tested. I'm also learning c# sharp side by side and improving my scripting skills as much as possible in the process. I also understand that you usually want to mock out any dependencies when it comes to unit testing a method that relies on another method or class. In this case it's a function or cmdlet since it's powershell.(Learning these side by side shows how much more I can do in c#)
All I'm asking for is a little help identifying things that need to be mocked and how many tests should I write? I know that sounds subjective but I'm trying to get a general idea and best practice for myself. I keep hearing from my co-workers that TDD is the way to go and it does speed up the development/scripting process significantly by catching bugs early that you're going to run in to anyway but don't have to test manually. I'm trying to learn to do tests first but need a little bit of guidance. I apologize if I seem like a scrub. I just need validation that I'm learning correctly.
To make my questions clearer:
Am I identifying what I need to mock correctly?
What am I missing or could identify better?
Am I identifying what I need to test correctly?
What I'm looking for in an answer:
Here's what you're Missing/Screwed up/Could identify better
The Code I'm Testing
Function 1
function New-ServiceSnapshot {
[CmdletBinding()]
param (
# path to export to
[Parameter(Mandatory=$true)]
[string]$ServiceExportPath
)
$results = get-service
$results | Export-Csv -Path $ServiceExportPath -NoTypeInformation
}
On function 1 at first glance I look at it and can see 2 dependencies. I would want to mock out get-service and use dummy data to do this. I did it by an import-csv from real dummy data in to the real object that would be tested. In order to test the export I have no clue. I would maybe mock out the return for the input ExportPath parameter? Just return the string path that was input? Would that suffice?
Function 2
function Inspect-ServiceSnapshot {
[CmdletBinding()]
param (
#Snapshot
[Parameter(Mandatory=$false)]
[ValidateScript({Test-Path $_ -PathType Leaf})]
[string]$SnapshotPath,
# timer
[Parameter(Mandatory=$false)]
[int]$TimeToWait = 0,
# If variable
[Parameter(Mandatory=$false)]
[object[]]
$SnapshotVariable,
# compare variable
[Parameter(Mandatory=$false)]
[object[]]
$CompareVariable
)
if($TimeToWait) { Start-Sleep -Seconds $TimeToWait }
if($SnapshotVariable){$snap = $SnapshotVariable}
if($SnapShotPath){$snap = Import-Csv -Path $SnapshotPath}
if($CompareVariable){$Compare = $CompareVariable}else{$Compare = (Get-Service)}
if($null -eq $CompareVariable -and $null -ne $SnapshotVariable){Write-Error -Message "If you have a SnapshotVariable, Compare variable is required" -ErrorAction Stop}
if($null -eq $SnapshotVariable -and $null -ne $CompareVariable){Write-Error -Message "If you have a CompareVariable, SnapshotVariable is required" -ErrorAction Stop}
$list = #()
foreach($entry in $Compare) {
# make sure we are dealing with the SAME service
$previousStatus = $snap | Where-Object { $_.Name -eq $entry.Name} | Select-Object -Property Status -first 1 -ExpandProperty Status
$info = New-Object -TypeName psobject -Property #{
Name = $entry.Name
CurrentStatus = $entry.status
DisplayName = $entry.displayname
StartType = $entry.StartType
PreviousStatus = if ($null -ne $previousStatus) { $previousStatus } else { 'Didnt Exist before, New Service' }
isDifferent = if($previousStatus -ne $entry.status) {$true} elseif($null -eq $previousStatus) {$null} else { $false}
}
$list += $info
}
$list | Where-Object { $_.isDifferent -eq $true -and $_.StartType -ne "Automatic"}
}
On this I think I would want to test all 4 of the inputs. I also would have to mock the dependencies and some of the inputs since they require real objects for dummy data. Other than that. No Clue.

Related

PowerShell booleans -- How to handle null differently from false?

I'm trying to write a server build script that includes Boolean parameters for various tasks, e.g. whether to install IIS. If the user does not specify this parameter one way or the other, I want the script to prompt for a decision, but for convenience and unattended execution I want the user to be able to explicitly choose to install IIS or NOT install IIS by setting the value to True or False on the command line and therefore avoid being prompted. My issue is that when I create a Boolean parameter, PowerShell automatically sets it to False, rather than leaving it null, if it wasn't specified on the command line. Here is the design that I THOUGHT would've worked:
param(
[bool]$IIS
)
if ($IIS -eq $null) {
$InstallIIS = Read-Host "Do you want to install IIS? (Y/N)"
if ($InstallIIS -eq "Y") {$IIS = $true}
}
if ($IIS) {Do stuff here}
Any suggestions for how to achieve my desired outcome would be most appreciated. Then if this changes anything, what I'd REALLY like to do is leverage PSRemoting to accept these build decision parameters on the user's system host and then pass them to the targets as an ArgumentList, and I'm wondering if that will affect how these Booleans are handled. For example:
param (
[string[]]$Computers
[bool]$IIS
)
$Computers | Foreach-Object {
Invoke-Command -ComputerName $_ -ArgumentList $IIS -ScriptBlock {
param(
[bool]$IIS
)
if ($IIS -eq $null) {
$InstallIIS = Read-Host "Do you want to install IIS? (Y/N)"
if ($InstallIIS -eq "Y") {$IIS = $true}
}
if ($IIS) {Do stuff here}
Ideas?
The way to accomplish this is with Parameter Sets:
[CmdletBinding()]
param (
[Parameter()]
[string[]]$Computers ,
[Parameter(ParameterSetName = 'DoSomethingWithIIS', Mandatory = $true)]
[bool]$IIS
)
$Computers | Foreach-Object {
Invoke-Command -ArgumentList $IIS -ScriptBlock {
param(
[bool]$IIS
)
if ($PSCmdlet.ParameterSetName -ne 'DoSomethingWithIIS') {
$InstallIIS = Read-Host "Do you want to install IIS? (Y/N)"
if ($InstallIIS -eq "Y") {$IIS = $true}
}
if ($IIS) {Do stuff here}
Well of course even though I Googled about this quite a bit before posting here, including discovering the [AllowNull()] parameter and finding that it did NOT help in my use case, I ended up finding the answer in the first Google search AFTER posting. This is what worked:
[nullable[bool]]$IIS
My only gripe with that syntax is that running Get-Help against the script now returns shows this for the IIS parameter:
-IIS <Nullable`1>
instead of:
-IIS <Boolean>
But unless there's a more elegant way to achieve what I need, I think I can live with that by adding a useful description for that parameter as well as Examples.
Even though boolean operators handle $null, $False, '', "", and 0 the same, you can do an equality comparison to see which is which.
If ($Value -eq $Null) {}
ElseIf ($Value -eq $False) {}
..etc..
In your situation, you want to use [Switch]$IIS. This will be $False by default, or $True if entered with the command a la Command -IIS, then you can handle it in your code like:
If ($IIS) {}
Which will only be $True if entered at the command line with -IIS
Instead of using an equality test that's going to try to coerce the value to make the test work:
if ($IIS -eq $Null)
Use -is to check the type directly:
PS C:\> $iis = $null
PS C:\> $iis -is [bool]
False

Missing AD module and can't get it, need something similar or something to simulate it

So I'm trying to output a complete KB list for all computers on a server (which works on one computer) but it doesn't recognize Get-ADcomputer as a cmdlet. When checking various sources, it appears that the AD module isn't included. As I'm doing this on a work computer/server I'm hesitant to download anything or anything of that nature.
Is there any way I can achieve the following without using the AD module or someway I might be missing how to import the module (if it exists, which I don't think it does on this system)?
# 1. Define credentials
$cred = Get-Credential
# 2. Define a scriptblock
$sb = {
$Session = New-Object -ComObject Microsoft.Update.Session
$Searcher = $Session.CreateUpdateSearcher()
$HistoryCount = $Searcher.GetTotalHistoryCount()
$Searcher.QueryHistory(0,$HistoryCount) | ForEach-Object -Process {
$Title = $null
if ($_.Title -match "\(KB\d{6,7}\)") {
# Split returns an array of strings
$Title = ($_.Title -split '.*\((?<KB>KB\d{6,7})\)')[1]
} else {
$Title = $_.Title
}
$Result = $null
switch ($_.ResultCode) {
0 { $Result = 'NotStarted'}
1 { $Result = 'InProgress' }
2 { $Result = 'Succeeded' }
3 { $Result = 'SucceededWithErrors' }
4 { $Result = 'Failed' }
5 { $Result = 'Aborted' }
default { $Result = $_ }
}
New-Object -TypeName PSObject -Property #{
InstalledOn = Get-Date -Date $_.Date;
Title = $Title;
Name = $_.Title;
Status = $Result
}
} | Sort-Object -Descending:$false -Property InstalledOn | Where {
$_.Title -notmatch "^Definition\sUpdate"
}
}
#Get all servers in your AD (if less than 10000)
Get-ADComputer -ResultPageSize 10000 -SearchScope Subtree -Filter {
(OperatingSystem -like "Windows*Server*")
} | ForEach-Object {
# Get the computername from the AD object
$computer = $_.Name
# Create a hash table for splatting
$HT = #{
ComputerName = $computer ;
ScriptBlock = $sb ;
Credential = $cred;
ErrorAction = "Stop";
}
# Execute the code on remote computers
try {
Invoke-Command #HT
} catch {
Write-Warning -Message "Failed to execute on $computer because $($_.Exception.Message)"
}
} | Format-Table PSComputerName,Title,Status,InstalledOn,Name -AutoSize
You've got 3 options:
First is to just install the RSAT feature for AD which will include the AD module. This is probably the best option unless there is something specific preventing it. If you're running your script from a client operating systems you need to install the RSAT first, though.
Option 2 (which should only be used if adding the Windows feature is somehow an issue) is to download and use the Quest AD tools, which give very similar functionality, but it looks like Dell is doing their best to hide these now so that may be difficult to locate...
Option 3 is to use the .NET ADSI classes to access AD directly, which will work without any additional downloads on any system capable of running PowerShell. If you'd like to go this route you should check out the documentation for the interface Here and for the System.DirectoryServices namespace Here.
Edit
Just noticed the last part of your question, what do you mean by "a complete KB list"? Not just Windows updates or things updated manually or whatever? What else would be in a list of Windows updates that was not a Windows update?
You have not mentioned the OSes you are using but in general if you have a server 2008 R2 or above, all you have to do it activate the RSAT feature AD PowerShell Module and you will have the cmdlet you are looking for.
On a client machine, you 'have to' install RSAT, and then activate the features. You can take a look at the technet article for more info: https://technet.microsoft.com/en-us/library/ee449483(v=ws.10).aspx
If you don't want to use that option, then you will have to use .NET ADSI classes. There are tons of examples on how to do this, it basically boils down to a couple of lines really. Technet has examples on this as well: https://technet.microsoft.com/en-us/library/ff730967.aspx

Accept lists of computers in many forms in a Powershell pipeline

I am trying to create a script that accepts lists of computers in various forms from many different 3rd party sources that I don't control. These various sources return computers sometimes as a simple array of strings, sometimes as a Powershell Object, sometimes as a hash. I want my script to take any of these types of lists and get the computer name(s), put it in an array. Then perform the actual processing.
Here are some examples of data that might be given to my script that I am trying to create.
#('system-01','system-02') | \\path\share\mycommand.ps1
#(#{"ComputerName" = "system-01";OtherKey = 'foo'},
#{"ComputerName" = "system-02";OtherKey = 'foo'}) | \\path\share\mycommand.ps1
#([PSCustomObject]#{
ComputerName = 'system-01'
OtherProperties = 'foo'
},
[PSCustomObject]#{
ComputerName = 'system-02'
OtherProperties = 'foo'
}) | \\path\share\mycommand.ps1
My script currently looks like.
[CmdletBinding()]
param(
[Parameter(Mandatory=$True,
ValueFromPipeline=$True,
ParameterSetName="ComputerHash")]
[hashtable]$ComputerHash,
[Parameter(Mandatory=$True,
ValueFromPipeline=$True,
ParameterSetName="ComputerArray")]
[String[]]$ComputerName,
[Parameter(Mandatory=$True,
ValueFromPipeline=$True,
ParameterSetName="ComputerObject")]
[Object[]]$ComputerObjects
)
BEGIN
{
# create an empty array to build up a list of the computers
$computers=#()
}
PROCESS
{
# get all the computers from the pipeline
switch ($PsCmdlet.ParameterSetName)
{
"ComputerArray" { $computers+=$ComputerName; break}
"ComputerHash" { $computers+=$ComputerHash.ComputerName; break}
"ComputerObject"{ $computers+=$ComputerObjects.ComputerName; break}
}
}
END
{
$computers | % {
# do the stuff
"Do something on $_"
}
}
Unfortunately, I am currently getting the Parameter set cannot be resolved ... error. How do I make my script so that it will basically accept any kind of pipeline input, and then do the right thing? Is there some simpler method I should be using?
What you could do, instead of making different parameter sets, is to just deal with it after you get the info. So something like:
[CmdletBinding()]
param(
[Parameter(Mandatory=$True,
ValueFromPipeline=$True)]
$ComputerList
)
BEGIN
{
# create an empty array to build up a list of the computers
$computers=Switch($ComputerList.GetType()){
{$_.Name -eq 'Hashtable'}{$ComputerList.ComputerName;Continue}
{$ComputerList -is [Array] -and $ComputerList[0] -is [String]}{$ComputerList;Continue}
{$ComputerList -is [Array] -and $ComputerList[0] -is [Object]}{$ComputerList.ComputerName}
}
}
PROCESS
{
}
END
{
$computers | % {
# do the stuff
"Do something on $_"
}
}
Or maybe even easier:
BEGIN{$Computers=If($ComputerList[0] -is [String]){$ComputerList}Else{$ComputerList.ComputerName}}
Edit: As pointed out, we don't get the piped data in the BEGIN or END blocks, so my idea works, but my script doesn't. Instead we have to do things in the PROCESS block as Zoredache has stated. He already posted his code, and I'm sure it works wonderfully, but I figured I'd post a modified version of mine so my answer wouldn't continue to be wrong, because, well, I don't like having wrong answers out there.
Function Test-Function1{
[CmdletBinding()]
param(
[Parameter(Mandatory=$True,
ValueFromPipeline=$True)]
$ComputerList
)
BEGIN
{
}
PROCESS
{
[string[]]$computers+=Switch($ComputerList){
{$_ -is [Hashtable]}{$_.ComputerName;Continue}
{$_ -is [String]}{$_;Continue}
{$_ -is [Object]}{$_|Select -Expand Computer*}
}
}
END
{
$computers | % {
# do the stuff
"Do something on $_"
}
}
}
Then when I piped data to it as such:
#{'ComputerName'='Server1','Server2'}|Test-Function1
[pscustomobject]#{'Computer'='Server1'},[pscustomobject]#{'ComputerName'='Server2'}|Test-Function1
'Server1','Server2'|Test-Function1
They all responded with the expected output of:
Do something on Server1
Do something on Server2
#TheMadTechnician pointed me in the right direction, but his code seemed to have some errors, and didn't quiet work the way I wanted. Here is the code that seems to do everything I want.
[CmdletBinding()]
param(
[Parameter(Mandatory=$True,
ValueFromPipeline=$True)]
$PipelineItem
)
BEGIN
{
$ComputerList=#()
}
PROCESS
{
$ComputerList+=
Switch($PipelineItem.GetType()) {
{$_.Name -eq 'String'}
{$PipelineItem}
{$_.Name -eq 'Hashtable'}
{$PipelineItem.ComputerName}
{$_.Name -eq 'PSCustomObject' -and (Get-Member -MemberType Properties -Name "Computer" -InputObject $PipelineItem)}
{$PipelineItem.Computer}
{$_.Name -eq 'PSCustomObject' -and (Get-Member -MemberType Properties -Name "ComputerName" -InputObject $PipelineItem)}
{$PipelineItem.ComputerName}
}
}
END
{
$ComputerList | % {
# do the stuff
"Do something on $_"
}
}

How can you test if an object has a specific property?

How can you test if an object has a specific property?
Appreciate I can do ...
$members = Get-Member -InputObject $myobject
and then foreach through the $members, but is there a function to test if the object has a specific property?
Additional Info:
The issue is I'm importing two different sorts of CSV file, one with two columns, the other with three. I couldn't get the check to work with "Property", only with "NoteProperty" ... whatever the difference is
if ( ($member.MemberType -eq "NoteProperty" ) -and ($member.Name -eq $propertyName) )
Like this?
[bool]($myObject.PSobject.Properties.name -match "myPropertyNameToTest")
You can use Get-Member
if (Get-Member -inputobject $var -name "Property" -Membertype Properties) {
#Property exists
}
This is succinct and readable:
"MyProperty" -in $MyObject.PSobject.Properties.Name
We can put it in a function:
function HasProperty($object, $propertyName)
{
$propertyName -in $object.PSobject.Properties.Name
}
For me MyProperty" -in $MyObject.PSobject.Properties.Name didn't work, however
$MyObject.PSobject.Properties.Name.Contains("MyProperty")
works
There are a number of solutions to this question that work in strict mode, but some are better than others.
Solutions that do not appear to iterate through every property are the fastest solutions.
Bernie White's solution and
esskar's solution (modified)
Solutions that look as though they iterate through every property are slower.
sebke CCU's solution and
dan-gph's solution
The solution that appears to iterate through every property and uses a regular expression is a little slower than the previous two solutions (because compiling and executing the regular expression takes more time)
CB.'s solution
The solution that uses GetMethod appears to iterate through every property, but its use of GetMethod makes it significantly slower.
Paul's GetMethod solution
The following script was used to compare the previously mentioned solutions in strict mode:
# Tested in PowerShell core 7.2.0
Set-StrictMode -Version Latest
$propertyExistsMethods = New-Object System.Collections.Generic.Dictionary'[string,scriptblock]'
# Fastest
$propertyExistsMethods.Add(
"PSObject.Properties (Bernie White's solution)",
{
Param( [PSObject] $Object, [string] $Property )
[bool]$Object.PSObject.Properties[$Property]
})
$propertyExistsMethods.Add(
"PSObject.Properties.Item (esskar's solution (modified))",
{
Param( [PSObject] $Object, [string] $Property )
[bool]$Object.PSObject.Properties.Item($property)
})
# Not as fast
$propertyExistsMethods.Add(
"Contains (sebke CCU's solution)",
{
Param( [PSObject] $Object, [string] $Property )
$Object.PSobject.Properties.Name.Contains($Property)
})
$propertyExistsMethods.Add(
"-in (dan-gph's solution)",
{
Param( [PSObject] $Object, [string] $Property )
$Property -in $Object.PSobject.Properties.Name
})
# Slower than the previously mentioned solutions
$propertyExistsMethods.Add(
"-match (CB.'s solution)",
{
Param( [PSObject] $Object, [string] $Property )
[bool]($Object.PSobject.Properties.name -match $Property)
})
# Slowest
$propertyExistsMethods.Add(
"GetMember (Paul's solution)",
{
Param( [PSObject] $Object, [string] $Property )
Get-Member -inputobject $Object -name $Property -Membertype Properties
})
foreach ($method in $propertyExistsMethods.Keys) {
$propertyExists = $propertyExistsMethods[$method]
$o = #{}
foreach ($i in 1..100000) {
$o[$i] = "p$i"
}
Write-Host $method
$measure = Measure-Command {
foreach ($i in 1..100000) {
# Always check for a property that does NOT exist
& $propertyExists -Object $o -Property 'p'
}
}
Write-Host $measure | % { $_.Milliseconds }
Write-Host ''
}
The output is as follows:
PSObject.Properties (Bernie White's solution)
00:00:03.1437587
PSObject.Properties.Item (esskar's solution)
00:00:03.5833642
Contains (sebke CCU's solution)
00:00:04.4812702
-in (dan-gph's solution)
00:00:04.6507811
-match (CB.'s solution)
00:00:05.1107066
GetMember (Paul's solution)
00:00:14.5305115
Try this for a one liner that is strict safe.
[bool]$myobject.PSObject.Properties[$propertyName]
For example:
Set-StrictMode -Version latest;
$propertyName = 'Property1';
$myobject = [PSCustomObject]#{ Property0 = 'Value0' };
if ([bool]$myobject.PSObject.Properties[$propertyName]) {
$value = $myobject.$propertyName;
}
I've been using the following which returns the property value, as it would be accessed via $thing.$prop, if the "property" would be to exist and not throw a random exception. If the property "doesn't exist" (or has a null value) then $null is returned: this approach functions in/is useful for strict mode, because, well, Gonna Catch 'em All.
I find this approach useful because it allows PS Custom Objects, normal .NET objects, PS HashTables, and .NET collections like Dictionary to be treated as "duck-typed equivalent", which I find is a fairly good fit for PowerShell.
Of course, this does not meet the strict definition of "has a property".. which this question may be explicitly limited to. If accepting the larger definition of "property" assumed here, the method can be trivially modified to return a boolean.
Function Get-PropOrNull {
param($thing, [string]$prop)
Try {
$thing.$prop
} Catch {
}
}
Examples:
Get-PropOrNull (Get-Date) "Date" # => Monday, February 05, 2018 12:00:00 AM
Get-PropOrNull (Get-Date) "flub" # => $null
Get-PropOrNull (#{x="HashTable"}) "x" # => "HashTable"
Get-PropOrNull ([PSCustomObject]#{x="Custom"}) "x" # => "Custom"
$oldDict = New-Object "System.Collections.HashTable"
$oldDict["x"] = "OldDict"
Get-PropOrNull $d "x" # => "OldDict"
And, this behavior might not [always] be desired.. ie. it's not possible to distinguish between x.Count and x["Count"].
Just check against null.
($myObject.MyProperty -ne $null)
If you have not set PowerShell to StrictMode, this works even if the property does not exist:
$obj = New-Object PSObject;
Add-Member -InputObject $obj -MemberType NoteProperty -Name Foo -Value "Bar";
$obj.Foo; # Bar
($obj.MyProperty -ne $null); # False, no exception
If you are using StrictMode and the psobject might be empty, it will give you an error.
For all purposes this will do:
if (($json.PSobject.Properties | Foreach {$_.Name}) -contains $variable)
I find this method more strict and faster when checking multiple properties
$null -ne $myobject.PSObject.Properties.Item("myPropertyNameToTest")
Real similar to a javascript check:
foreach($member in $members)
{
if($member.PropertyName)
{
Write $member.PropertyName
}
else
{
Write "Nope!"
}
}
Just to clarify
given the following object
$Object
With the following properties
type : message
user : john.doe#company.com
text :
ts : 11/21/2016 8:59:30 PM
The following are true
$Object.text -eq $NULL
$Object.NotPresent -eq $NULL
-not $Object.text
-not $Object.NotPresent
So the earlier answers that explicitly check for the property by name is the most correct way to verify that that property is not present.
I ended up with the following function ...
function HasNoteProperty(
[object]$testObject,
[string]$propertyName
)
{
$members = Get-Member -InputObject $testObject
if ($members -ne $null -and $members.count -gt 0)
{
foreach($member in $members)
{
if ( ($member.MemberType -eq "NoteProperty" ) -and `
($member.Name -eq $propertyName) )
{
return $true
}
}
return $false
}
else
{
return $false;
}
}
I recently switch to set strict-mode -version 2.0 and my null tests failed.
I added a function:
#use in strict mode to validate property exists before using
function exists {
param($obj,$prop)
try {
if ($null -ne $obj[$prop]) {return $true}
return $false
} catch {
return $false
}
return $false
}
Now I code
if (exists $run main) { ...
rather than
if ($run.main -ne $null) { ...
and we are on our way. Seems to work on objects and hashtables
As an unintended benefit it is less typing.
for me this work
Set-StrictMode -Version Latest
$TMP = ...
$HAS_SERVERS=($TMP | Select-Object Servers)
if (-not $HAS_SERVERS.Servers){
echo "No servers. Abort."
} else {
...
}
I just started using PowerShell with PowerShell Core 6.0 (beta) and following simply works:
if ($members.NoteProperty) {
# NoteProperty exist
}
or
if (-not $members.NoteProperty) {
# NoteProperty does not exist
}
You could check with:
($Member.PropertyNames -contains "Name") this will check for the Named property
For identifying which of the objects in an array have a property
$HasProperty = $ArrayOfObjects | Where-Object {$_.MyProperty}

How to run a command against multiple servers simultaneously in Powershell

I am looking for a way to restart three services on multiple servers simultaneously. I know how to restart services against a list of servers by using a loop but as I have many servers it would take a long time to wait for each service on each server to restart in a sequential order. Is there a way to send restart service command to all servers at once instead of waiting for each server?
You could try to work with jobs. Jobs are run in the background and you have to retrieve them with Get-Job to see their status. Please read the information to Powershell jobs on these two sites:
http://msdn.microsoft.com/en-us/library/dd878288%28v=vs.85%29.aspx
http://technet.microsoft.com/de-DE/library/hh847783.aspx
Your code would look something like this:
$servernames | ForEach-Object {Start-Job -Name "Job-$_" -Scriptblock {"Enter your code here -Computername $_"}}
This will create a background job for each servername. As already mentioned you can see the status using the cmdlet Get-Job. To get the result use the cmdlet Receive-Job.
you can use the invoke-command cmdlet
invoke-command -computername computer1,computer2,computer3 {restart-service servicename}
I use and improove a multi-thread Function, you can use it like :
$Script = {
param($Computername)
restart-service servicename -Computername $Computername
}
#('Srv1','Srv2') | Run-Parallel -ScriptBlock $Script
include this code in your script
function Run-Parallel {
<#
.Synopsis
This is a quick and open-ended script multi-threader searcher
http://www.get-blog.com/?p=189#comment-28834
Improove by Alban LOPEZ 2016
.Description
This script will allow any general, external script to be multithreaded by providing a single
argument to that script and opening it in a seperate thread. It works as a filter in the
pipeline, or as a standalone script. It will read the argument either from the pipeline
or from a filename provided. It will send the results of the child script down the pipeline,
so it is best to use a script that returns some sort of object.
.PARAMETER ScriptBlock
This is where you provide the PowerShell ScriptBlock that you want to multithread.
.PARAMETER ItemObj
The ItemObj represents the arguments that are provided to the child script. This is an open ended
argument and can take a single object from the pipeline, an array, a collection, or a file name. The
multithreading script does it's best to find out which you have provided and handle it as such.
If you would like to provide a file, then the file is read with one object on each line and will
be provided as is to the script you are running as a string. If this is not desired, then use an array.
.PARAMETER InputParam
This allows you to specify the parameter for which your input objects are to be evaluated. As an example,
if you were to provide a computer name to the Get-Process cmdlet as just an argument, it would attempt to
find all processes where the name was the provided computername and fail. You need to specify that the
parameter that you are providing is the "ComputerName".
.PARAMETER AddParam
This allows you to specify additional parameters to the running command. For instance, if you are trying
to find the status of the "BITS" service on all servers in your list, you will need to specify the "Name"
parameter. This command takes a hash pair formatted as follows:
#{"key" = "Value"}
#{"key1" = "Value"; "key2" = 321; "key3" = 1..9}
.PARAMETER AddSwitch
This allows you to add additional switches to the command you are running. For instance, you may want
to include "RequiredServices" to the "Get-Service" cmdlet. This parameter will take a single string, or
an aray of strings as follows:
"RequiredServices"
#("RequiredServices", "DependentServices")
.PARAMETER MaxThreads
This is the maximum number of threads to run at any given time. If ressources are too congested try lowering
this number. The default value is 20.
.PARAMETER SleepTimer_ms
This is the time between cycles of the child process detection cycle. The default value is 200ms. If CPU
utilization is high then you can consider increasing this delay. If the child script takes a long time to
run, then you might increase this value to around 1000 (or 1 second in the detection cycle).
.PARAMETER TimeOutGlobal
this is the TimeOut in second for listen the last thread, after this timeOut All thread are closed, only each other are returned
.PARAMETER TimeOutThread
this is the TimeOut in second for each thread, the thread are aborted at this time
.PARAMETER PSModules
List of PSModule name to include for use in ScriptBlock
.PARAMETER PSSapins
List of PSSapin name to include for use in ScriptBlock
.EXAMPLE
1..20 | Run-Parallel -ScriptBlock {param($i) Start-Sleep $i; "> $i sec <"} -TimeOutGlobal 15 -TimeOutThread 5
.EXAMPLE
Both of these will execute the scriptBlock and provide each of the server names in AllServers.txt
while providing the results to GridView. The results will be the output of the child script.
gc AllServers.txt | Run-Parallel $ScriptBlock_GetTSUsers -MaxThreads $findOut_AD.ActiveDirectory.Servers.count -PSModules 'PSTerminalServices' | out-gridview
#>
Param(
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
$ItemObj,
[ScriptBlock]$ScriptBlock = $null,
$InputParam = $Null,
[HashTable] $AddParam = #{},
[Array] $AddSwitch = #(),
$MaxThreads = 20,
$SleepTimer_ms = 100,
$TimeOutGlobal = 300,
$TimeOutThread = 100,
[string[]]$PSSapins = $null,
[string[]]$PSModules = $null,
$Modedebug = $true
)
Begin{
$ISS = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
ForEach ($Snapin in $PSSapins){
[void]$ISS.ImportPSSnapIn($Snapin, [ref]$null)
}
ForEach ($Module in $PSModules){
[void]$ISS.ImportPSModule($Module)
}
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $ISS, $Host)
$RunspacePool.CleanupInterval=1000
$RunspacePool.Open()
$Jobs = #()
}
Process{
#ForEach ($Object in $ItemObj){
if ($ItemObj){
Write-Host $ItemObj -ForegroundColor Yellow
$PowershellThread = [powershell]::Create().AddScript($ScriptBlock)
If ($InputParam -ne $Null){
$PowershellThread.AddParameter($InputParam, $ItemObj.ToString()) | out-null
}Else{
$PowershellThread.AddArgument($ItemObj.ToString()) | out-null
}
ForEach($Key in $AddParam.Keys){
$PowershellThread.AddParameter($Key, $AddParam.$key) | out-null
}
ForEach($Switch in $AddSwitch){
$PowershellThread.AddParameter($Switch) | out-null
}
$PowershellThread.RunspacePool = $RunspacePool
$Handle = $PowershellThread.BeginInvoke()
$Job = [pscustomobject][ordered]#{
Handle = $Handle
Thread = $PowershellThread
object = $ItemObj.ToString()
Started = Get-Date
}
$Jobs += $Job
}
#}
}
End{
$GlobalStartTime = Get-Date
$continue = $true
While (#($Jobs | Where-Object {$_.Handle -ne $Null}).count -gt 0 -and $continue) {
ForEach ($Job in $($Jobs | Where-Object {$_.Handle.IsCompleted -eq $True})){
$out = $Job.Thread.EndInvoke($Job.Handle)
$out # return vers la sortie srandard
#Write-Host $out -ForegroundColor green
$Job.Thread.Dispose() | Out-Null
$Job.Thread = $Null
$Job.Handle = $Null
}
foreach ($InProgress in $($Jobs | Where-Object {$_.Handle})) {
if ($TimeOutGlobal -and (($(Get-Date) - $GlobalStartTime).totalseconds -gt $TimeOutGlobal)){
$Continue = $false
#Write-Host $InProgress -ForegroundColor magenta
}
if (!$Continue -or ($TimeOutThread -and (($(Get-Date) - $InProgress.Started).totalseconds -gt $TimeOutThread))) {
$InProgress.thread.Stop() | Out-Null
$InProgress.thread.Dispose() | Out-Null
$InProgress.Thread = $Null
$InProgress.Handle = $Null
#Write-Host $InProgress -ForegroundColor red
}
}
Start-Sleep -Milliseconds $SleepTimer_ms
}
$RunspacePool.Close() | Out-Null
$RunspacePool.Dispose() | Out-Null
}
}