Parallel run for this particular powershell script - powershell

I am in the process of re-writing the script below to be able to run in parallel, as can be seen in the code, an array of servers is passed to the script, and then it loads it onto a hash table, loops through each server at a time to do the deployment, for each server there are files to execute in a particular order (see array of files). Looking at the structure, I feel workspace is the way to go here but I could be wrong.
Where the performance gains can be seen in my opinion or having the code such that multiple servers can be executed at thesame time rather than waiting for each server to complete and move onto the next one. foreach parallel
I ran a test to call a function declared outside a workspace, it worked.Is this good practice to call a function declared outside a workspace ? I ask this because I would like to reuse some functions outside the workspace, or is it generally better to put all the code in the workspace even ones that are not intended for parallel workloads i.e one off calls to the code. ?
The below is the code I am testing with.
Function Check-Instance-Connection{
param
(
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
$sql_server,
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=1)]
$db_name
)
try
{
#Return extra useful info by using custom objects
$check_outcome = "" | Select-Object -Property log_date, stage, status, error_message
$check_outcome.log_date = (Get-Date)
$check_outcome.stage = 'Ping SQL instance for $sql_server'
#test connection for a sql instance
$connectionstring = "Data Source=$sql_server;Integrated Security =true;Initial Catalog=$db_name;Connect Timeout=5;"
$sqllconnection = New-Object System.Data.SqlClient.SqlConnection $connectionstring
$sqllconnection.Open();
$check_outcome.status = $true
$check_outcome.error_message = ''
return $check_outcome
}
Catch
{
$check_outcome.status = $false
$check_outcome.error_message = $_.Exception.Message
return $check_outcome
}
finally{
$sqllconnection.Close();
}
}
$file_list = #("deployment_1.sql","deployment_2.sql","deployment_3.sql","deployment_4.sql","deployment_5.sql")
$x = (1,"Server1",3,1),(4,"Server2",6,2),(3,"Server3",4,3)
$k = 'serverid','servername','locationid','appid' # key names correspond to data positions in each array in $x
$h = #{}
For($i=0;$i -lt $x[0].length; $i++){
$x |
ForEach-Object{
[array]$h.($k[$i]) += [string]$_[$i]
}
}
$folder = "F:\Files\"
$database_name = "Test"
foreach ($server_id in $all_server_ids)
{
$severid = $h["serverid"][$all_server_ids.indexof($server_id)]
$servername = $h["servername"][$all_server_ids.indexof($server_id)]
$locationid = $h["locationid"][$all_server_ids.indexof($server_id)]
$message = 'ServerID {0} has a servername of {1} and a location id of {2}' -f $server_id, $h["servername"][$all_server_ids.indexof($server_id)],$h["locationid"][$all_server_ids.indexof($server_id)]
Write-Output $message
Write-Output "This $severid and this $servername and this $locationid"
foreach ($file in $file_list)
{
$is_instance_ok = Check-Instance-Connection $servername $database_name
if ($is_instance_ok.check_outcome -eq $true){
invoke-sqlcmd -ServerInstance "$servername" -inputfile $folder$file -Database "$database_name" -Querytimeout 60 -OutputSqlErrors $true -ConnectionTimeout 10 -ErrorAction Continue -Errorvariable generated_error | Out-Null
}
}
}

Thanks, I did a lot more research and looked at a lot of examples on how workflows work. This is what I have come up with.
Workflow RunExecution
{
Function Check-Instance-Connection{
param
(
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
$sql_server,
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=1)]
$db_name
)
try
{
#Return extra useful info by using custom objects
$check_outcome = "" | Select-Object -Property log_date, stage, status, error_message
$check_outcome.log_date = (Get-Date)
$check_outcome.stage = 'Ping SQL instance for $sql_server'
#test connection for a sql instance
$connectionstring = "Data Source=$sql_server;Integrated Security =true;Initial Catalog=$db_name;Connect Timeout=5;"
$sqllconnection = New-Object System.Data.SqlClient.SqlConnection $connectionstring
$sqllconnection.Open();
$check_outcome.status = $true
$check_outcome.error_message = ''
return $check_outcome
}
Catch
{
$check_outcome.status = $false
$check_outcome.error_message = $_.Exception.Message
return $check_outcome
}
finally{
$sqllconnection.Close();
}
}
$file_list = #("deployment_1.sql","deployment_2.sql","deployment_3.sql","deployment_4.sql","deployment_5.sql")
$x = (1,"server1\DEV3",3,1),(4,"serer1\DEV2",6,2),(3,"serer2\DEV1",4,3)
$k = 'serverid','servername','locationid','appid'
$h = #{}
For($i=0;$i -lt $x[0].length; $i++){
$x |
ForEach-Object{
[array]$h.($k[$i]) += [string]$_[$i]
}
}
$folder = "C:\Temp\"
$database_name = "Test"
$all_server_ids = $h['serverid']
foreach -parallel ($server_id in $all_server_ids)
{
$severid = $h["serverid"][$all_server_ids.indexof($server_id)]
$servername = $h["servername"][$all_server_ids.indexof($server_id)]
$locationid = $h["locationid"][$all_server_ids.indexof($server_id)]
foreach ($file in $file_list)
{
# $check_fine = $is_instance_ok.check_outcome
# if ($check_fine = $true){
invoke-sqlcmd -ServerInstance "$servername" -inputfile $folder$file -Database "$database_name" -Querytimeout 60 -OutputSqlErrors $true -ConnectionTimeout 10 -ErrorAction Continue
write-output "invoke-sqlcmd -ServerInstance $servername -inputfile $folder$file -Database $database_name -Querytimeout 60 -OutputSqlErrors $true -ConnectionTimeout 10 -ErrorAction Continue "
# }
}
}
}
RunExecution

Related

How to download a webstring from multiple web-servers in parallel via Powershell?

I need to download some webcontent from many servers in parallel as part of a scheduled job, but I cannot find a correct way to run the download in parallel/async. How can this be done?
Without any parallelism I can do it this way, but it is very slow:
$web = [System.Net.WebClient]::new()
[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# $srvList is a list of servers of viariable length
$allData = ""
foreach ($srv in $srvList) {
$url = "https:\\$srv\MyWebPage"
$data = $web.DownloadString($url)
$allData += $data
}
But how to do this in parallel via "$web.DownloadStringAsync"?
I found this snippet, but I dont see how to get the result of each call and how to concatenate it:
$job = Register-ObjectEvent -InputObject $web -EventName DownloadStringCompleted -Action {
Write-Host 'Download completed'
write-host $EventArgs.Result
}
$web.DownloadString($url)
Does someone know, how to get this solved in a short & smart way?
The best and fastest way is using runspaces:
Add-Type -AssemblyName System.Collections
$GH = [hashtable]::Synchronized(#{})
[System.Collections.Generic.List[PSObject]]$GH.results = [System.Collections.Generic.List[string]]::new()
[System.Collections.Generic.List[string]]$GH.servers = #('server1','server2');
[System.Collections.Generic.List[string]]$GH.functions = #('Download-Content');
[System.Collections.Generic.List[PSObject]]$jobs = #()
#-----------------------------------------------------------------
function Download-Content {
#-----------------------------------------------------------------
# a function which runs parallel
param(
[string]$server
)
$web = [System.Net.WebClient]::new()
[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$url = "https:\\$server\MyWebPage"
$data = $web.DownloadString($url)
$GH.results.Add( $data )
}
#-----------------------------------------------------------------
function Create-InitialSessionState {
#-----------------------------------------------------------------
param(
[System.Collections.Generic.List[string]]$functionNameList
)
# Setting up an initial session state object
$initialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
foreach( $functionName in $functionNameList ) {
# Getting the function definition for the functions to add
$functionDefinition = Get-Content function:\$functionName
$functionEntry = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $functionName, $functionDefinition
# And add it to the iss object
[void]$initialSessionState.Commands.Add($functionEntry)
}
return $initialSessionState
}
#-----------------------------------------------------------------
function Create-RunspacePool {
#-----------------------------------------------------------------
param(
[InitialSessionState]$initialSessionState
)
$runspacePool = [RunspaceFactory]::CreateRunspacePool(1, ([int]$env:NUMBER_OF_PROCESSORS + 1), $initialSessionState, $Host)
$runspacePool.ApartmentState = 'MTA'
$runspacePool.ThreadOptions = "ReuseThread"
[void]$runspacePool.Open()
return $runspacePool
}
#-----------------------------------------------------------------
function Release-Runspaces {
#-----------------------------------------------------------------
$runspaces = Get-Runspace | Where { $_.Id -gt 1 }
foreach( $runspace in $runspaces ) {
try{
[void]$runspace.Close()
[void]$runspace.Dispose()
}
catch {
}
}
}
$initialSessionState = Create-InitialSessionState -functionNameList $GH.functions
$runspacePool = Create-RunspacePool -initialSessionState $initialSessionState
foreach ($server in $GH.servers)
{
Write-Host $server
$job = [System.Management.Automation.PowerShell]::Create($initialSessionState)
$job.RunspacePool = $runspacePool
$scriptBlock = { param ( [hashtable]$GH, [string]$server ); Download-Content -server $server }
[void]$job.AddScript( $scriptBlock ).AddArgument( $GH ).AddArgument( $server )
$jobs += New-Object PSObject -Property #{
RunNum = $jobCounter++
JobObj = $job
Result = $job.BeginInvoke() }
do {
Sleep -Seconds 1
} while( $runspacePool.GetAvailableRunspaces() -lt 1 )
}
Do {
Sleep -Seconds 1
} While( $jobs.Result.IsCompleted -contains $false)
$GH.results
Release-Runspaces | Out-Null
[void]$runspacePool.Close()
[void]$runspacePool.Dispose()
Finally I found a simple solution via events. Here is my code-snippet:
cls
Remove-Variable * -ea 0
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = $null
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$srvList = #('srv1','srv2','srv3')
$webObjList = [System.Collections.ArrayList]::new()
$eventList = [System.Collections.ArrayList]::new()
$resultList = [System.Collections.ArrayList]::new()
$i=0
foreach ($srv in $srvList) {
$null = $webObjList.add([System.Net.WebClient]::new())
$null = $eventList.add($(Register-ObjectEvent -InputObject $webObjList[$i] -EventName DownloadStringCompleted -SourceIdentifier $srv))
$null = $resultList.add($webObjList[$i].DownloadStringTaskAsync("https://$srv/MyWebPage"))
$i++
}
do {sleep -Milliseconds 10} until ($resultList.IsCompleted -notcontains $false)
foreach ($srv in $srvList) {Unregister-Event $srv}
# show all Results:
$resultList.result

Insert a new Mongo row using PowerShell

I have a PowerShell script that has been vexing me all day. I've finally gotten to the point where I can get a collection but I'm getting an error that I can't figure out.
function Get-MongoDBCollection {
Param(
$database,
$CollectionName,
$settings = $null, #[MongoDB.Driver.MongoCollectionSetting]
$returnType = [PSOBJECT]
)
$method = $database.GetType().GetMethod('GetCollection')
$gericMethod = $method.MakeGenericMethod($returnType)
$gericMethod.Invoke($database,[object[]]($CollectionName,$settings))
}
$dbName = "MyDatabaseName"
$collectionName = "MyCollectionName"
try {
add-type -path 'C:\Program Files\MongoDB\Drivers\System.Runtime.InteropServices.RuntimeInformation.4.0.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll'
Add-Type -Path "C:\Program Files\MongoDB\Drivers\MongoDB.Bson.2.6.0\lib\net45\MongoDB.Bson.dll"
add-type -path "C:\Program Files\MongoDB\Drivers\DnsClient.1.0.7\lib\net45\DnsClient.dll";
Add-Type -path "C:\Program Files\MongoDB\Drivers\MongoDB.Driver.Core.2.6.0\lib\net45\MongoDb.Driver.Core.dll"
Add-Type -Path "C:\Program Files\MongoDB\Drivers\MongoDB.Driver.2.6.0\lib\net45\MongoDB.Driver.dll"
}
catch {
$_;
$_.Exception.LoaderExceptions
}
$connectionString = "mongodb://localhost:27018";
$mongoClient = new-object MongoDb.Driver.MongoClient($connectionString);
$mongoDatabase = $mongoclient.GetDatabase($dbName)
$mongoDatabase.GetCollection($collectionname)
$collection = Get-MongoDBCollection $mongodatabase "SharePoint" -returnType ([MongoDB.Bson.BsonDocument]);
$datafile = Get-Content -Raw -Path "D:\datafiles\86fba866-77ed-4f40-4637-08d57d2e25b4.json" #`| ConvertFrom-Json
[MongoDB.Bson.BsonDocument] $doc = [MongoDB.Bson.BsonDocument]::Parse($datafile);
$x = $collection.InsertOne($doc)
The script takes the contents of a file, which contains a JSON string and converts it to BsonDocument and then tries to insert it. I'm getting the following error.
Argument types do not match
At line:1 char:1
+ $collection.InsertOneAsync($doc)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], ArgumentException
+ FullyQualifiedErrorId : System.ArgumentException
What am I doing wrong here?
This is a tough cookie! Basically, powershell doesn't support generics in a trivial way.. It supports them, but in a complex and hard to understand way!
I found this snippet in another stack overflow post days ago but can't find it at this moment. It was an unpleasant search/trek to begin with. The snippet from the original author who i wish i could give proper credit is as follows:
###
# Usage:
# $Collection = Get-MongoDBCollection $database 'collectionName'
# or
# $Collection = Get-MongoDBCollection $database 'collectionName' -returnType ([MongoDB.Bson.BsonDocument])
function Get-MongoDBCollection {
Param(
$database,
$CollectionName,
$settings = $null, #[MongoDB.Driver.MongoCollectionSetting]
$returnType = [PSOBJECT]
)
$method = $database.GetType().GetMethod('GetCollection')
$gericMethod = $method.MakeGenericMethod($returnType)
$gericMethod.Invoke($database,[object[]]($CollectionName,$settings))
}
And here's the full context usage I had below. I also have to load the DnsClient.dll dependencies myself. Throughout the process, per usual, the powershell module failed to provide helpful errors.
PS: This is amateur powershell, only used .ps because i needed to interface Office365! No promise of best practice!
#########
# Globals
##
$mongoDbDriverPath = "Z:\Work\mb-rule-watch\lib\net45"
$dbName = "mbRules"
$collectionName = "Mailboxes"
#########
# Load o365 credentials/modules
##
Import-Module MsOnline
if( ! $credential ){
Write-Host "Requesting Credentials - Use o365 Admin Account"
$credential = Get-Credential
Connect-MsolService -Credential $credential
}
# Prep remote session connection
if( ! $session ){
$session = New-PSSession `
-ConfigurationName Microsoft.Exchange `
-ConnectionUri https://outlook.office365.com/powershell-liveid/ `
-Credential $credential `
-Authentication Basic `
-AllowRedirection
# import commands from Microsoft Exchange Server shell
Import-PSSession $session
}
###########
# Functions
##
###
# Usage:
# $Collection = Get-MongoDBCollection $database 'collectionName'
# or
# $Collection = Get-MongoDBCollection $database 'collectionName' -returnType ([MongoDB.Bson.BsonDocument])
function Get-MongoDBCollection {
Param(
$database,
$CollectionName,
$settings = $null, #[MongoDB.Driver.MongoCollectionSetting]
$returnType = [PSOBJECT]
)
$method = $database.GetType().GetMethod('GetCollection')
$gericMethod = $method.MakeGenericMethod($returnType)
$gericMethod.Invoke($database,[object[]]($CollectionName,$settings))
}
###########
# MAIN
##
try
{
# Load mongo driver
Add-Type -Path "$($mongoDbDriverPath)\DnsClient.dll"
Add-Type -Path "$($mongoDbDriverPath)\MongoDB.Bson.dll"
Add-Type -Path "$($mongoDbDriverPath)\MongoDB.Driver.Core.dll"
Add-Type -Path "$($mongoDbDriverPath)\MongoDB.Driver.dll"
# Connect to mongo
$client = new-object -TypeName MongoDB.Driver.MongoClient -ArgumentList "mongodb://localhost"
# Get DB handle
[MongoDB.Driver.IMongoDatabase] $db = $client.GetDatabase( $dbName );
# Aquire Collection handle with brute force generic hacks Via a PS god on stackoverflow.
$collection = Get-MongoDBCollection $db $collectionName -returnType ([MongoDB.Bson.BsonDocument])
#foreach( $mbx in $( Get-Mailbox -ResultSize Unlimited -identity example_user_id ) ){
foreach( $mbx in $( Get-Mailbox -ResultSize Unlimited ) ){
$identityStr = $mbx.identity
$rules = Get-InboxRule -Mailbox $identityStr
# convert some huge ints (>Mongo Int64) to strings
foreach( $rule in $rules ){
$rule.RuleIdentity = "" + $rule.RuleIdentity + ""
}
# Json Stringify
$rules_json = ConvertTo-Json $rules
# If the mailbox had rules
if( $rules_json ){
write-host( "Inserting rules for: " + $identityStr )
# Cache results to FS this time.
echo $rules_json > var\rules\$identityStr.rules.json
try{
# Type convert/parse our json string
$document = new-object -TypeName MongoDB.Bson.BsonDocument
$document = [MongoDb.Bson.BsonDocument]::Parse( '{ "_username":"' + $identityStr + '","rules": ' + $rules_json + '}' );
# Insert the JSON document
$collection.InsertOne( $document )
} catch {
Write-Host "JSON parse or mongo insert failure"
foreach( $x IN $_.Exception ){
foreach( $msg IN $x ){
Write-Error $msg
}
}
}
}
}
}
catch
{
Write-Host "Script errors occured"
if( $_.Exception.LoaderExceptions ){
Write-Host "!!Dependency Loads Failed!!"
foreach( $msg IN $_.Exception.LoaderExceptions ){
Write-Error $msg
}
} else {
foreach( $x IN $_.Exception ){
foreach( $msg IN $x ){
Write-Error $msg
}
}
}
}

Cannot bind argument to parameter 'InputObject' because it is null

I have a powershell script that measures download time on some pages, however I get the error above, I am unsure what I am doing wrong
error is
Cannot bind argument to parameter 'InputObject' because it is null.
function ResponseTime($CommonName,$URL, $environment)
{
$Times = 5
$i = 0
$TotalResponseTime = 0
Write-HOst $URL
While ($i -lt $Times) {
$Request = New-Object System.Net.WebClient
$Request.UseDefaultCredentials = $true
$Start = Get-Date
Write-HOst $URL
$PageRequest = $Request.DownloadString($URL)
$TimeTaken = ((Get-Date) - $Start).TotalMilliseconds
$Request.Dispose()
$i ++
$TotalResponseTime += $TimeTaken
}
$AverageResponseTime = $TotalResponseTime / $i
Write-Host Request to $CommonName took $AverageResponseTime ms in average -ForegroundColor Green
$details = #{
Date = get-date
AverageResponseTime = $AverageResponseTime
ResponseTime = $Destination
Environment = $environment
}
$results += New-Object PSObject -Property $details
$random = Get-Random -minimum 1 -maximum 30
Start-Sleep -s $random
}
#PRODUCTION
ResponseTime -commonname 'app homepage' -URL 'https://url1' -environment 'PRODUCTION'
ResponseTime -commonname 'department homepage' -URL 'https://url2' -environment 'PRODUCTION'
$results | export-csv -Path c:\so.csv -NoTypeInformation
Reviewing your last edit, it seems that $results simply returns $null (As your error says)
The only line setting $results is $results += New-Object PSObject -Property $details
It is not in the scope of your Export-CSV call and - even if it would, $results could be empty, if this line is not called.
You should IMHO set it to e.g. an ArrayList like follows:
$results = New-Object -TypeName System.Collections.ArrayList
And add items to it via
$times = ResponseTime -commonname '' #etc
$results.Add($times) | Out-Null
This gives you an ArrayList - even if there are no items in it - which can easily be transformed to CSV and other formats.
#Clijsters has given the correct answer; i.e. the issue being the scope of your $results variable.
This answer just provides a bit of a code review to help you with other bits going forwards...
function Get-ResponseTime {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$CommonName
,
[Parameter(Mandatory = $true)]
[string]$URL
,
[Parameter(Mandatory = $true)]
[string]$Environment
,
[Parameter(Mandatory = $false)]
[int]$Times = 5
)
[System.Int64]$TotalResponseTime = 0
[System.Diagnostics.Stopwatch]$stopwatch = New-Object 'System.Diagnostics.Stopwatch'
Write-Verbose "Processing URL: $URL"
1..$times | foreach-object {
[System.Net.WebClient]$Request = New-Object 'System.Net.WebClient'
$Request.UseDefaultCredentials = $true
Write-Verboset "Call $_ to URL: $URL"
$stopwatch.Restart()
$PageRequest = $Request.DownloadString($URL)
$stopwatch.Stop()
$TimeTaken = $stopwatch.Elapsed.TotalMilliseconds
$Request.Dispose()
$TotalResponseTime += $TimeTaken
}
$AverageResponseTime = $TotalResponseTime / $Times
Write-Verbose "Request to $CommonName took $AverageResponseTime ms on average"
$details = #{
Date = get-date
AverageResponseTime = $AverageResponseTime
#ResponseTime = $Destination #this is not declared anywhere / don't know what this field's for
Environment = $environment
}
Write-Output (New-Object 'PSObject' -Property $details)
#do you really want a delay here? Doesn't make much sense... may make sense to include a delay in the above loop; i.e. to stagger your tests?
#$random = Get-Random -minimum 1 -maximum 30
#Start-Sleep -s $random
}
#PRODUCTION
[PSObject[]]$results = #(
(Get-ResponseTime -commonname 'app homepage' -URL 'https://url1' -environment 'PRODUCTION' -Verbose)
,(Get-ResponseTime -commonname 'department homepage' -URL 'https://url2' -environment 'PRODUCTION' -Verbose)
)
$results | Export-Csv -LiteralPath 'c:\so.csv' -NoTypeInformation
Use verb-noun function names (e.g. Get-Item). What is the naming convention for Powershell functions with regard to upper/lower case usage?
Use "Cmdlets" (Advanced Functions) instead of (Basic) Functions; they're basically the same thing, only tagged with [Cmdletbinding()]. The reason for this you get support for functionality such as verbose output. http://www.lazywinadmin.com/2015/03/standard-and-advanced-powershell.html
Use a stopwatch to time processes (you could also use measure-command; but any output would be suppressed / consumed by the measure-command function). Timing a command's execution in PowerShell
Have your cmdlet output its values to the pipeline via Write-Output (or you can leave off the function name; any output caused by placing a variable with nothing to process it will be fed to the pipeline; i.e. write-object $a is the same as a line solely consisting of $a).
Capture the output into your $results variable outside of the function, and handle the results there.

Only prints the last server in the list, I want all servers

This only prints the last server in the list, I'm looking to get all servers and print to screen
$machines = (Get-BrokerMachine -AdminAddress $adminaddress -DesktopGroupName $deliverygroup | Select-Object DNSname).DNSname
foreach($machine in $machines){
$machinelist = Get-BrokerMachine -HostedMachineName $machine
if($machinelist.InMaintenanceMode -eq $true){
$status = "$machine is in maintenance mode"
}else {
$status = "$machine is not in maintenance mode"
}
}
Write-Host $status
Here is a more PowerShell-like approach (not tested):
Get-BrokerMachine -AdminAddress $adminaddress -DesktopGroupName $deliverygroup | ForEach-Object {
$machineName = $_.DNSName
[PSCustomObject] #{
"MachineName" = $machineName
"MaintenanceMode" = (Get-BrokerMachine -HostedMachineName $machine).InMaintenanceMode
}
} | Export-Csv "C:\whatever\results.csv" -NoTypeInformation
$Status is constantly being overwritten by the current machine in your list.
You're looking for:
$Status+=
As opposed to:
$Status=
You'll also want to explicitly state that $Status will be an array at the beginning like so:
$Status=#()
Or when you create the variable and omit the line at the beginning.
[array]$Status +=
Otherwise, you'll get results that run together as it will be treated as a [String]
another funky mode :
function get-BrokerMachineMode
{
param (
[Parameter(Mandatory = $true)]
[string[]]$machines
)
begin
{
$ErrorActionPreference = 'Stop'
Add-Type -Language CSharp #"
public class BrokenBroker {
qpublic System.String MachineName;
public System.String MaintenanceMode;
public BrokenBroker (string MachineName, string MaintenanceMode)
{
this.MachineName = MachineName;
this.MaintenanceMode = IsInMaintenanceMode;
}
}
"#
$status = #()
Write-Verbose "Created objects..."
}
process
{
try
{
$machines = (Get-BrokerMachine -AdminAddress $adminaddress `
-DesktopGroupName $deliverygroup | Select-Object DNSname).DNSname
foreach ($machine in $machines)
{
Write-Verbose "Checking machine: $machine"
$machinelist = Get-BrokerMachine -HostedMachineName $machine
if ($machinelist.InMaintenanceMode -eq $true)
{
$status += New-Object BrokenBroker($machine, $true)
}
else
{
$status += New-Object BrokenBroker($machine, $false)
}
}
}
catch
{
Write-Error $error[0].Exception.Message
}
$status
}
end
{
Write-Verbose "Done"
}
}
this is a function you just must to load then you can launch it just by using this command:
$computers = get-content = {PATH TO TXT FILE}
$list = get-BrokerMachineMode -machines $computers -Verbose

Powershell Timeout After two Seconds

I'm new to powershell. I read some lines on www.powershell.com. Now I need your help to solve a problem. I want to read the UUID from clients in the Network. Therefore I created a document "pcs.txt" where all PCs are stored.
$pc = Get-Content pcs.txt #Read content of file
$cred = Get-Credential “domain\user”
for ($i=0; $i -lt $pc.length; $i++) {
$Result=test-connection -ComputerName $pc[$i] -Count 1 -Quiet
If ($Result -eq 'True')
{
$uuid = (Get-WmiObject Win32_ComputerSystemProduct -ComputerName $pc[$i] -Credential $cred).UUID
$Ausgabe=$pc[$i] + ';'+$uuid
$Ausgabe
}
else
{
$Ausgabe=$pc[$i] + '; UUID nicht erhalten'
$Ausgabe
}
}
First I test if the ping works. When the ping works I try to get the uuid.
Sometimes I don't get the uuid even if the ping worked. So I would like to code a timeout, which say -> go to next pc when you don't have the uuid after 2 seconds.
Can you help me please?
Alas, there is no timeout parameter for Get-WmiObject commandlet. There is a feature request in MS Connect, but it is from 2011 and still open.
A workaround, which I haven't tested is available by using System.Management. I'll copy-and-paste it here in case the link goes dead. (And I hate SO answers that only contain links to resouces that may or may not exist...)
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
}
I found a good workaround!
http://theolddogscriptingblog.wordpress.com/2012/05/11/wmi-hangs-and-how-to-avoid-them/
Here my working code:
$pc = Get-Content pcs.txt #FILE FROM THE HARDDISK
$cred = Get-Credential “DOMAIN\USER” #
for ($i=0; $i -lt $pc.length; $i++)
{
$Result=test-connection -ComputerName $pc[$i] -Count 1 -Quiet
If ($Result -eq 'True')
{
$WMIJob = Get-WmiObject Win32_ComputerSystemProduct -ComputerName $pc[$i] -Credential $cred -AsJob
$Timeout=Wait-Job -ID $WMIJob.ID -Timeout 1 # the Job times out after 1 seconds.
$uuid = Receive-Job $WMIJob.ID
if ($uuid -ne $null)
{
$Wert =$uuid.UUID
$Ausgabe=$pc[$i] + ';'+$Wert
$Ausgabe
}
else
{
<#$b = $error | select Exception
$E = $b -split (:)
$x = $E[1]
$Error.Clear() #>
$Ausgabe=$pc[$i] + '; got no uuid'
$Ausgabe
}
}
else
{
$Ausgabe='PC not reached through ping.'
$Ausgabe
}
}
I hope I can help somebody with that