Skip to content

Instantly share code, notes, and snippets.

@MVKozlov
Last active December 19, 2023 13:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MVKozlov/607a7ae0faa8a6390b6b32595776c79c to your computer and use it in GitHub Desktop.
Save MVKozlov/607a7ae0faa8a6390b6b32595776c79c to your computer and use it in GitHub Desktop.
Run selected script for computer list and save results in a file. Can be used multiple times until all collected
<#
.SYNOPSIS
Run selected script for computer list and save results in a file. Can be used multiple times until all collected
.DESCRIPTION
Run the specified script for the list of computers and save the launch results to the Clixml file
The result file contains the script being launched (Script) and the results of previous executions (Data).
Restarting works only for computers with no success
If the file has already been created, only the path to the file is needed for repeated runs
PoshRSJob module required for parallel script execution
.PARAMETER Path
Path to the file with the results
.PARAMETER Script
The script to run. The computer name is passed to the script
.PARAMETER ComputerList
List of processed computers. Adds to an already existing list
.PARAMETER ArgumentHash
Hash of @{ComputerName=ArgumentList} additional arguments for every computer. Adds to an already existing hash
.PARAMETER Force
Overwrite the computer in the list if it already exists there (and therefore overrun the script)
.PARAMETER Remove
Remove the listed computers from the results
.PARAMETER RunFilter
Restart the script only for computers whose name falls under the filter
.PARAMETER MaxConcurrentJob
The number of simultaneously executed copies of the script
.PARAMETER PingTimeout
Ping timeout
.EXAMPLE
# Collect (first):
$c = Get-ADComputer -SearchBase 'ou=SOME_OU,dc=domain, dc=local' -Filter * | Where-Object { $_.Enabled }
Invoke-DataCollector.ps1 -Path d:\diskdrivesdata.xml -ComputerList $c.Name -Script `
{ param($comp) $cim = New-CimSession $comp; if ($cim) { Get-Disk -CimSession $cim } else { throw "Can't create cim session " } }
# Repeat periodically until all results are successful (until all data is collected)
Invoke-DataCollector.ps1 -Path d:\diskdrivesdata.xml
# Export and use it:
$data = Import-Clixml D:\diskdrivesdata.xml
$mydata = $data.Data.Values | Where-Object { $_.Status -eq 'Success' } | Select-Object -Expand $_.Data
$mydata | Export-Csv -Delimiter ';' -Encoding UTF8 -NoTypeInformation -Path d:\diskdrivesdata.csv
.NOTES
Requires Posh-RSJob module
Author: Max Kozlov
.LINK
https://gist.github.com/MVKozlov/607a7ae0faa8a6390b6b32595776c79c
#>
#require Posh-RSJob
[CmdletBinding(DefaultParameterSetName='collect', SupportsShouldProcess=$true, ConfirmImpact='High')]
param(
[Parameter(Position=0, Mandatory = $true)]
[string]$Path,
[Parameter(ParameterSetName = 'collect')]
[scriptblock]$Script,
[Parameter(ParameterSetName = 'collect')]
[Parameter(ParameterSetName = 'remove', Mandatory = $true)]
[string[]]$ComputerList,
[Parameter(ParameterSetName = 'collect')]
[hashtable]$ArgumentHash = @{},
[Parameter(ParameterSetName = 'collect')]
[string]$RunFilter,
[Parameter(ParameterSetName = 'collect')]
[switch]$Force,
[Parameter(ParameterSetName = 'collect')]
[int]$MaxConcurrentJob = 10,
[Parameter(ParameterSetName = 'collect')]
[int]$PingTimeout = 500,
[Parameter(ParameterSetName = 'remove')]
[switch]$Remove
)
function Ping-ComputerAsync {
param(
[Alias('Name', 'FullDomainName')]
[string[]][Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0)]$ComputerName,
[int]$Timeout = 200,
[switch]$Quiet
)
BEGIN {
$wt1 = 0; $wt2 = 0
[System.Threading.ThreadPool]::GetMinThreads([ref]$wt1, [ref]$wt2)
[void][System.Threading.ThreadPool]::SetMinThreads(256, $wt2)
$Tasks = {}.Invoke()
}
PROCESS {
foreach ($comp in $ComputerName) {
# AD Workaround
$comp = $comp -replace '\$$'
Write-Verbose ('Testing {0}' -f $comp)
$Tasks.Add([PSCustomObject] @{
ComputerName = $comp
Task = (New-Object System.Net.NetworkInformation.Ping).SendPingAsync($comp, $Timeout)
})
}
}
END {
try {
[Threading.Tasks.Task]::WaitAll($Tasks.Task)
}
catch {}
[void][System.Threading.ThreadPool]::SetMinThreads($wt1, $wt2)
$Tasks | Foreach-Object {
$o = New-Object -TypeName PSObject -Property @{ ComputerName = $_.ComputerName }
#$o | Add-Member -MemberType AliasProperty -Name Name -Value ComputerName
$o | Add-Member -Name "Status" -MemberType NoteProperty -Value "NotFound" -PassThru |
Add-Member -Name "Address" -MemberType NoteProperty -Value "" -PassThru |
Add-Member -Name "Name" -MemberType AliasProperty -Value ComputerName -PassThru |
Add-Member -Name "PSComputerName" -MemberType AliasProperty -Value ComputerName -PassThru |
Add-Member -Name "RoundtripTime" -MemberType NoteProperty -Value -1
if ($_.Task.IsFaulted) {
#$o.Status = $_.Task.Exception.InnerException.InnerException.Message
}
else {
$o.Status = $_.Task.Result.Status; $o.Address=$_.Task.Result.Address; $o.RoundtripTime = $_.Task.Result.RoundtripTime
}
if ($Quiet) { if ($o.Status -eq 'Success') { $o.ComputerName } }
else { $o }
}
}
}
try {
$data = Import-Clixml $Path
Write-Host "Data readed from $Path" -ForegroundColor Cyan
$Script = [scriptblock]::Create($data.Script)
}
catch {
Write-Host $_ -ForegroundColor Red
Write-Host "Creating new data" -ForegroundColor Cyan
$data = [PSCustomObject]@{
Script = $Script
Data = @{}
}
}
if ($PSCmdlet.ParameterSetName -eq 'remove' -and $ComputerList) {
foreach ($n in $ComputerList) {
if ($PSCmdlet.ShouldProcess($n, "Remove from Data file" )) {
Write-Host "Remove computer $n"
$data.Data.Remove($n)
}
}
$data | Export-Clixml -Path $Path
Write-Host "Data saved to $Path" -ForegroundColor Cyan
return
}
if ($null -eq $Script) {
throw "Script to run is mandatory on first launch"
}
if ($ComputerList) {
foreach ($ComputerName in $ComputerList) {
if (!$data.Data.ContainsKey($ComputerName) -or $Force) {
$data.Data[$ComputerName] = [PSCustomObject]@{
ComputerName = $ComputerName
Status = 'Unknown'
Arguments = @()
Data = $null
}
}
}
}
if ($ArgumentHash) {
foreach ($arg in $ArgumentHash.GetEnumerator()) {
if ($data.Data.ContainsKey($arg.Key) -and (-not $data.Data[$arg.key].Arguments -or $Force)) {
$data.Data[$arg.key].Arguments = [array]$arg.Value
}
}
}
if ($RunFilter) {
Write-Host "RunFilter: $RunFilter" -ForegroundColor Cyan
}
$batch = (Split-Path Path) -replace '\.*$'
[array]$comps = $data.Data.GetEnumerator() | Where-Object { $_.Value.Status -ne 'Success' } |
Where-Object { -not $RunFilter -or $_.Key -match $RunFilter } |
ForEach-Object { $_.Key }
$avail = $null
if ($comps) {
Write-Host "Check $($comps.Count) computer(s) for availability" -ForegroundColor Cyan
$comps = $comps | Ping-ComputerAsync -Timeout $PingTimeout
$na = $comps | Where-Object { $_.Status -ne 'Success'}
if ($na) {
$data.Data[$na.ComputerName] |
ForEach-Object {
Write-Host "Skip $($_.ComputerName) because it not available" -ForegroundColor Gray
$_.Status = 'Not Available'
}
}
$avail = $comps | Where-Object { $_.Status -eq 'Success'}
}
if ($avail) {
$jobs = $data.Data[$avail.ComputerName] |
ForEach-Object {
Write-Host "Start script job for $($_.ComputerName)" -ForegroundColor DarkGreen
$argumentList = ,$_.ComputerName + $_.Arguments
Start-RSJob -ScriptBlock $Script -ArgumentList $argumentList -Throttle $MaxConcurrentJob -Batch $batch -Name $_.ComputerName
}
Write-Host "Waiting for Jobs"
$jobs | Wait-RSJob | ForEach-Object {
if ($_.HasErrors) {
Write-Host "Save Error to $($_.Name) - $($_.Error)" -ForegroundColor Red
$data.Data[$_.Name].Data = $_.Error
$data.Data[$_.Name].Status = 'Error'
}
else {
Write-Host "Save Success to $($_.Name)" -ForegroundColor Green
$data.Data[$_.Name].Data = $_ | Receive-RSJob
$data.Data[$_.Name].Status = 'Success'
}
$_ | Remove-RSJob
}
}
$data | Export-Clixml -Path $Path
Write-Host "Data saved to $Path" -ForegroundColor Cyan
$data.Data.GetEnumerator() | Group-Object { $_.Value.Status } -NoElement | Out-Host
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment