I found this Multithreading script at http://www.get-blog.com/?p=189
Param($Command = $(Read-Host "Enter the script file"),
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]$ObjectList,
$InputParam = $Null,
$MaxThreads = 20,
$SleepTimer = 200,
$MaxResultTime = 120,
[HashTable]$AddParam = #{},
[Array]$AddSwitch = #()
)
Begin{
$ISS = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $ISS, $Host)
$RunspacePool.Open()
If ($(Get-Command | Select-Object Name) -match $Command){
$Code = $Null
}Else{
$OFS = "`r`n"
$Code = [ScriptBlock]::Create($(Get-Content $Command))
Remove-Variable OFS
}
$Jobs = #()
}
Process{
Write-Progress -Activity "Preloading threads" -Status "Starting Job $($jobs.count)"
ForEach ($Object in $ObjectList){
If ($Code -eq $Null){
$PowershellThread = [powershell]::Create().AddCommand($Command)
}Else{
$PowershellThread = [powershell]::Create().AddScript($Code)
}
If ($InputParam -ne $Null){
$PowershellThread.AddParameter($InputParam, $Object.ToString()) | out-null
}Else{
$PowershellThread.AddArgument($Object.ToString()) | out-null
}
ForEach($Key in $AddParam.Keys){
$PowershellThread.AddParameter($Key, $AddParam.$key) | out-null
}
ForEach($Switch in $AddSwitch){
$Switch
$PowershellThread.AddParameter($Switch) | out-null
}
$PowershellThread.RunspacePool = $RunspacePool
$Handle = $PowershellThread.BeginInvoke()
$Job = "" | Select-Object Handle, Thread, object
$Job.Handle = $Handle
$Job.Thread = $PowershellThread
$Job.Object = $Object.ToString()
$Jobs += $Job
}
}
End{
$ResultTimer = Get-Date
While (#($Jobs | Where-Object {$_.Handle -ne $Null}).count -gt 0) {
$Remaining = "$($($Jobs | Where-Object {$_.Handle.IsCompleted -eq $False}).object)"
If ($Remaining.Length -gt 60){
$Remaining = $Remaining.Substring(0,60) + "..."
}
Write-Progress `
-Activity "Waiting for Jobs - $($MaxThreads - $($RunspacePool.GetAvailableRunspaces())) of $MaxThreads threads running" `
-PercentComplete (($Jobs.count - $($($Jobs | Where-Object {$_.Handle.IsCompleted -eq $False}).count)) / $Jobs.Count * 100) `
-Status "$(#($($Jobs | Where-Object {$_.Handle.IsCompleted -eq $False})).count) remaining - $remaining"
ForEach ($Job in $($Jobs | Where-Object {$_.Handle.IsCompleted -eq $True})){
$Job.Thread.EndInvoke($Job.Handle)
$Job.Thread.Dispose()
$Job.Thread = $Null
$Job.Handle = $Null
$ResultTimer = Get-Date
}
If (($(Get-Date) - $ResultTimer).totalseconds -gt $MaxResultTime){
Write-Error "Child script appears to be frozen, try increasing MaxResultTime"
Exit
}
Start-Sleep -Milliseconds $SleepTimer
}
$RunspacePool.Close() | Out-Null
$RunspacePool.Dispose() | Out-Null
}
I'm confused as how to use it though. In particular how to use the $ObjectList variable. I would like to use this ping script that I wrote to ping a list of 100,000 machines... But in it's current form it pings about 100 machines a minute. So it would take over 16 hours to complete.
$Computers = Get-Content -Path C:\Temp\Comps.txt
foreach ($Computer in $Computers) {
if (test-Connection -ComputerName $Computer -Count 1) {
"$Computer is Pinging"
}
Else {
"$Computer is not Pinging"
}
}
Any pointers as to how to integrate the ping script with the multithreading script to yield faster results?
I'm assuming that I would remove the $Computers variable from the ping script, and instead use the $ObjectList variable in the multithreading script. But all of my attempts to do so fail.
You can ping hosts asynchronously with [System.Net.NetworkInformation.Ping], no need to go crazy with runspaces. Moreover it will ping the host once, so it will be way much faster than test-connection. Below is a sample for a batch of hosts (about 90). I guess it's not a good idea to load 100K at once, probably split it in smaller batches and do one by one.
$hosts = "www.facebook.com,www.twitter.com,www.youtu.be,www.google.com,www.youtube.com,www.instagram.com,www.linkedin.com,www.pinterest.com,www.wordpress.com,www.blogspot.com,www.apple.com,www.adobe.com,www.tumblr.com,www.amazon.com,www.vimeo.com,www.flickr.com,www.microsoft.com,www.yahoo.com,www.godaddy.com,www.qq.com,www.vk.com,www.reddit.com,www.baidu.com,www.nytimes.com,www.buydomains.com,www.wp.com,www.statcounter.com,www.jimdo.com,www.blogger.com,www.github.com,www.weebly.com,www.soundcloud.com,www.myspace.com,www.addthis.com,www.theguardian.com,www.cnn.com,www.stumbleupon.com,www.gravatar.com,www.digg.com,www.addtoany.com,www.creativecommons.org,www.paypal.com,www.yelp.com,www.imdb.com,www.huffingtonpost.com,www.feedburner.com,www.issuu.com,www.wixsite.com,www.wix.com,www.dropbox.com,www.forbes.com,www.amazonaws.com,www.washingtonpost.com,www.bluehost.com,www.etsy.com,www.go.com,www.msn.com,www.wsj.com,www.weibo.com,www.fc2.com,www.eventbrite.com,www.parallels.com,www.ebay.com,www.livejournal.com,www.reuters.com,www.taobao.com,www.typepad.com,www.bloomberg.com,www.elegantthemes.com,www.eepurl.com,www.usatoday.com,www.about.com,www.medium.com,www.macromedia.com,www.xing.com,www.bing.com,www.time.com,www.tripadvisor.com,www.aol.com,www.constantcontact.com,www.latimes.com,www.list-manage.com,www.webs.com,www.opera.com,www.live.com,www.bandcamp.com,www.bbc.com,www.businessinsider.com,www.dailymotion.com,www.cpanel.com,www.disqus.com,www.sina.com.cn,www.spotify.com,www.wired.com,www.googleusercontent.com"
$hosts = $hosts -split ","
$tasks = #{}
foreach ($h in $hosts) { $tasks[$h] = [System.Net.NetworkInformation.Ping]::new().SendPingAsync($h)}
Write-Host "Waiting for batch is completed" -NoNewline
while($false -in $tasks.Values.IsCompleted) {sleep -Milliseconds 300; Write-Host "." -NoNewline}
$result = foreach($h in $hosts) {
$r = $tasks[$h].Result
[PSCustomObject]#{
host = $h
address = $r.Address.IPAddressToString
status = if($r.Address.IPAddressToString){$r.Status}else{"Failed"}
time = $r.RoundtripTime
bytes = $r.Buffer.Count
ttl = $r.Options.Ttl
}
}
$result | Format-Table -AutoSize
This script, which I have adapted from the link and author in the synopsis, has good performance for me. I have modified it to take not only a range of ip addresses, but also an array of host names. Because of that, some of the variable names have poor names, but this is not production code so I have not invested in it beyond what I have below.
function Global:Ping-IPRange {
<#
.SYNOPSIS
Sends ICMP echo request packets to a range of IPv4 addresses between two given addresses.
.DESCRIPTION
This function lets you sends ICMP echo request packets ("pings") to
a range of IPv4 addresses using an asynchronous method.
Therefore this technique is very fast but comes with a warning.
Ping sweeping a large subnet or network with many swithes may result in
a peak of broadcast traffic.
Use the -Interval parameter to adjust the time between each ping request.
For example, an interval of 60 milliseconds is suitable for wireless networks.
The RawOutput parameter switches the output to an unformated
[System.Net.NetworkInformation.PingReply[]].
.INPUTS
None
You cannot pipe input to this funcion.
.OUTPUTS
The function only returns output from successful pings.
Type: System.Net.NetworkInformation.PingReply
The RawOutput parameter switches the output to an unformated
[System.Net.NetworkInformation.PingReply[]].
.NOTES
Author : G.A.F.F. Jakobs
Created : August 30, 2014
Version : 6
.EXAMPLE
Ping-IPRange -StartAddress 192.168.1.1 -EndAddress 192.168.1.254 -Interval 20
IPAddress Bytes Ttl ResponseTime
--------- ----- --- ------------
192.168.1.41 32 64 371
192.168.1.57 32 128 0
192.168.1.64 32 128 1
192.168.1.63 32 64 88
192.168.1.254 32 64 0
In this example all the ip addresses between 192.168.1.1 and 192.168.1.254 are pinged using
a 20 millisecond interval between each request.
All the addresses that reply the ping request are listed.
.EXAMPLE
Ping-IPRange -HostName "host1"
Ping-IPRange -HostName #('host1', 'host2')
Ping-IPRange -HostName #('www.microsoft.com', 'www.google.com')
.LINK
http://gallery.technet.microsoft.com/Fast-asynchronous-ping-IP-d0a5cf0e
#>
[CmdletBinding(ConfirmImpact='Low')]
Param(
[parameter(Mandatory = $true, Position = 0, ParameterSetName='range')]
[System.Net.IPAddress]$StartAddress,
[parameter(Mandatory = $true, Position = 1, ParameterSetName='range')]
[System.Net.IPAddress]$EndAddress,
[parameter(Mandatory = $true, Position = 0, ParameterSetName='list')]
[string[]]$HostName,
[int]$Interval = 30,
[Switch]$RawOutput = $false
)
$timeout = 2000
function New-Range ($start, $end) {
$addrList = [System.Collections.ArrayList]::new()
[byte[]]$BySt = $start.GetAddressBytes()
[Array]::Reverse($BySt)
[byte[]]$ByEn = $end.GetAddressBytes()
[Array]::Reverse($ByEn)
$i1 = [System.BitConverter]::ToUInt32($BySt,0)
$i2 = [System.BitConverter]::ToUInt32($ByEn,0)
for($x = $i1;$x -le $i2;$x++){
$ip = ([System.Net.IPAddress]$x).GetAddressBytes()
[Array]::Reverse($ip)
$null = $addrList.Add([System.Net.IPAddress]::Parse($($ip -join '.')))
}
,$addrList
}
if ($HostName)
{
$IPrange = #($HostName)
}
else
{
[System.Collections.ArrayList]$IPrange = New-Range $StartAddress $EndAddress
}
$IpTotal = $IPrange.Count
Get-Event -SourceIdentifier "ID-Ping*" | Remove-Event
Get-EventSubscriber -SourceIdentifier "ID-Ping*" | Unregister-Event
$index = 0
foreach ($ip in $IPrange){
if ($HostName)
{
[string]$VarName = "Ping_" + $ip + "_" + [guid]::NewGuid().ToString()
}
else
{
[string]$VarName = "Ping_" + $ip.Address
}
New-Variable -Name $VarName -Value (New-Object System.Net.NetworkInformation.Ping)
Register-ObjectEvent -InputObject (Get-Variable $VarName -ValueOnly) -EventName PingCompleted -SourceIdentifier "ID-$VarName"
(Get-Variable $VarName -ValueOnly).SendAsync($ip,$timeout,$VarName)
try{
$pending = (Get-Event -SourceIdentifier "ID-Ping*").Count
}catch [System.InvalidOperationException]{
Write-Verbose "Ping-IPrange : InvalidOperationException" -Verbose
}
finally{
Remove-Variable $VarName
}
#$index = [array]::indexof($IPrange,$ip)
if ($HostName)
{
Write-Progress -Activity "Sending ping to" -Id 1 -status $ip -PercentComplete (($index / $IpTotal) * 100)
}
else
{
Write-Progress -Activity "Sending ping to" -Id 1 -status $ip.IPAddressToString -PercentComplete (($index / $IpTotal) * 100)
}
Write-Progress -Activity "ICMP requests pending" -Id 2 -ParentId 1 -Status ($index - $pending) -PercentComplete (($index - $pending)/$IpTotal * 100)
Start-Sleep -Milliseconds $Interval
$index++
}
Write-Progress -Activity "Done sending ping requests" -Id 1 -Status 'Waiting' -PercentComplete 100
While($pending -lt $IpTotal){
Wait-Event -SourceIdentifier "ID-Ping*" | Out-Null
Start-Sleep -Milliseconds 10
$pending = (Get-Event -SourceIdentifier "ID-Ping*").Count
Write-Progress -Activity "ICMP requests pending" -Id 2 -ParentId 1 -Status ($IpTotal - $pending) -PercentComplete (($IpTotal - $pending)/$IpTotal * 100)
}
if($RawOutput){
$Reply = Get-Event -SourceIdentifier "ID-Ping*" | ForEach {
If($_.SourceEventArgs.Reply.Status -eq "Success"){
$_.SourceEventArgs.Reply
}
Unregister-Event $_.SourceIdentifier
Remove-Event $_.SourceIdentifier
}
}else{
$events = Get-Event -SourceIdentifier "ID-Ping*"
$Reply = [System.Collections.ArrayList]::new()
$i = 0
foreach ($event in $events) {
If($event.SourceEventArgs.Reply.Status -eq "Success"){
if ($HostName)
{
$null = $Reply.Add(
[PSCustomObject]#{
"HostName" = $HostName[$i]
"IPAddress" = $event.SourceEventArgs.Reply.Address
"Bytes" = $event.SourceEventArgs.Reply.Buffer.Length
"Ttl" = $event.SourceEventArgs.Reply.Options.Ttl
"ResponseTime" = $event.SourceEventArgs.Reply.RoundtripTime
}
)
}
else
{
$null = $Reply.Add(
[PSCustomObject]#{
"IPAddress" = $event.SourceEventArgs.Reply.Address
"Bytes" = $event.SourceEventArgs.Reply.Buffer.Length
"Ttl" = $event.SourceEventArgs.Reply.Options.Ttl
"ResponseTime" = $event.SourceEventArgs.Reply.RoundtripTime
}
)
}
}
else{
$addr = ($event.SourceIdentifier -split '_')[1]
$ip = ((([System.Net.IPAddress]$addr).IPAddressToString).Split('.'))
[Array]::Reverse($ip)
$ip = $ip -join '.'
if ($HostName)
{
$null = $Reply.Add(
[PSCustomObject]#{
"HostName" = $HostName[$i]
"IPAddress" = '0.0.0.0'
"Bytes" = -1
"Ttl" = -1
"ResponseTime" = -1
}
)
}
else
{
$null = $Reply.Add(
[PSCustomObject]#{
"IPAddress" = $ip
"Bytes" = -1
"Ttl" = -1
"ResponseTime" = -1
}
)
}
}
Unregister-Event $event.SourceIdentifier
Remove-Event $event.SourceIdentifier
$i++
}
}
if($Reply -eq $Null){
Write-Verbose "Ping-IPrange : No ip address responded" -Verbose
}
return ,$Reply
}
Related
I had coded a powershell executable in a evaluation windows server, where it worked fine. That is because it had Office (Excel) installed, hence it was possible to run $ExcelObj = New-Object -comobject Excel.Application. However, when moving on to the production server, my systems admin stressed that Office installation is strictly prohibited in the production server, making it not possible to execute the above command, and hence the remaining commands. I am not sure how to continue/change my code from this point onwards. I did search up and found this. But Its structure and method of writing to excel file is too "example", and am not able to figure out how to modify it to my preference. I really need this to work out.
Script.ps1:
#Exception for error hiding during debug
$ErrorActionPreference = 'SilentlyContinue'
#Variables
$PathFileCSV = "C:\Users\Administrator\Desktop\FinalPrototype.csv" #for actual
$PathFileXLSX = "C:\Users\Administrator\Desktop\FinalPrototype.xlsx" #for actual
#Function for file deletion (xlsx)
if ((Test-Path $PathFileXLSX)) {
Remove-Item $PathFileXLSX -Force
}
#Code to stop any running instance of excel, to prevent additional files from being created
# Stop-Process -processname excel -Force
#Function from Github (src:https://github.com/gangstanthony/PowerShell)
function Save-CSVasExcel {
param (
[string]$CSVFile = $(Throw 'No file provided.')
)
BEGIN {
function Resolve-FullPath ([string]$Path) {
if ( -not ([System.IO.Path]::IsPathRooted($Path)) ) {
# $Path = Join-Path (Get-Location) $Path
$Path = "$PWD\$Path"
}
[IO.Path]::GetFullPath($Path)
}
function Release-Ref ($ref) {
([System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) -gt 0)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
$CSVFile = Resolve-FullPath $CSVFile
$xl = New-Object -ComObject Excel.Application
}
PROCESS {
$wb = $xl.workbooks.open($CSVFile)
$xlOut = $CSVFile -replace '\.csv$', '.xlsx'
# can comment out this part if you don't care to have the columns autosized
# $ws = $wb.Worksheets.Item(1)
# $range = $ws.UsedRange
# [void]$range.EntireColumn.Autofit()
$num = 1
$dir = Split-Path $xlOut
$base = $(Split-Path $xlOut -Leaf) -replace '\.xlsx$'
$nextname = $xlOut
while (Test-Path $nextname) {
$nextname = Join-Path $dir $($base + "-$num" + '.xlsx')
$num++
}
$wb.SaveAs($nextname, 51)
}
END {
$xl.Quit()
$null = $ws, $wb, $xl | ForEach-Object { Release-Ref $_ }
del $CSVFile
}
}
#Function to create the CSV File
Get-ADUser -Filter * -Properties * -SearchBase "DC=devops,DC=company1" | select-object displayName, mail, #{n = 'OU'; e = { (($_.DistinguishedName -replace '^.*?,(?=[A-Z]{2}=)' -split ',')[0]).substring(3) } } | Export-CSV -Path $PathFileCSV -NoTypeInformation
#select-object #{name = "MemberOf"; expression = { ($_.memberof | ForEach-Object { (Get-ADObject $_).Name }) -join " | " } } --- for user roles
#select-object sAMAccountName --- for username
#Function to convert created CSV to XLS (With formatted column)
Save-CSVasExcel $PathFileCSV
#Function for file deletion (csv)
#if ((Test-Path $PathFileCSV)) {
# Remove-Item $PathFileCSV -Force
#}
#Beautification
$ExcelObj = New-Object -comobject Excel.Application
$ExcelWorkBook = $ExcelObj.Workbooks.Open($PathFileXLSX)
$ExcelWorkSheet = $ExcelWorkBook.Sheets.Item("FinalPrototype")
#$ExcelWorkSheet.Cells.Font.Name = "Comfortaa"
$ExcelWorkSheet.Cells.Font.Size = 11
#Function for finding user groups & more
$usersAMAccName = Get-ADUser -Filter * -Properties * -SearchBase "DC=devops,DC=company1" | select-object sAMAccountName
$hash.Clear()
$data = #('Name', 'Email', 'Organizational Unit')
$data += #(Get-ADGroup -filter * -searchbase "OU=DevOps User Groups,OU=C1,DC=devops,DC=company1" | Select-Object -expandproperty name )
foreach ($name in $usersAMAccName) {
$hash += [ordered]#{ $name.sAMAccountName = (Get-ADPrincipalGroupMembership $name.sAMAccountName | select-object name).name }
}
#Header-Row-only Beautification
$Rows = $data.Count
for ($i = 1; $i -le $Rows; $i++) {
$ExcelWorkSheet.Cells.Item(1, $i).Font.Size = 16
$ExcelWorkSheet.Cells.Item(1, $i) = $data[$i - 1]
$ExcelWorkSheet.Cells.Item(1, $i).EntireRow.Interior.ColorIndex = 6
}
#Insertion profile-related data
$range = $ExcelWorkSheet.UsedRange
$rangeROW = $ExcelWorkSheet.UsedRange.Rows.Count
$rangeCOL = $ExcelWorkSheet.UsedRange.Columns.Count
for ($i = 2; $i -le $rangeROW; $i++) {
for ($j = 4; $j -le $rangeCOL; $j++) {
if ($hash.($usersAMAccName[$i - 2] | select-object -expandproperty sAMAccountName) -contains $ExcelWorkSheet.Cells.Item(1, $j).value2()) {
$ExcelWorkSheet.Cells.Item($i, $j).Interior.ColorIndex = 4
$ExcelWorkSheet.Cells.Item($i, $j) = "Assigned"
}
else {
$ExcelWorkSheet.Cells.Item($i, $j).Interior.ColorIndex = 3
$ExcelWorkSheet.Cells.Item($i, $j) = "Not Assigned"
}
}
}
#Content-usedRange-only Beautification
$range.EntireColumn.Autofit()
#Non-Human user omission
for ($i = 1; $i -le $rangeROW; $i++) {
If ([string]::IsNullOrEmpty($ExcelWorkSheet.Cells.Item($i, 1).value2)) {
$ExcelWorkSheet.Cells.Item($i, 1).EntireRow.hidden = $true
}
}
#Beautification
$range.WrapText = "True"
$range.HorizontalAlignment = -4108
#Saving the final form of excel
$ExcelObj.DisplayAlerts = $FALSE
$ExcelWorkBook.Save()
$ExcelWorkBook.close($true)
#User Ref (Debug)
Clear-Host
Write-Host 'Debug: Reading' $PathFileXLSX 'now...'
$data = Import-Excel $PathFileXLSX
$data
Final .xlsx file when ran in evaluation server (with excel):
I have this code working well synchronously with powershell.Invoke() however with powershell.BeginInvoke() I am not able to capture the output. To use the below code you'll need to populate the $servers variable and it should run otherwise.
I tried to capture the output by adding each thread to the $threads variable while using EndInvoke() and I am able to see the thread handle and the iscompleted value, however I can't figure out where the value I am returning with the return portion of each function is stored.
The first block is the output I see, showing false for one async being finished until it finishes and then all thread handles show true.
Thanks!
8804 is True
16420 is True
13352 is True
11184 is True
3872 is True
8288 is True
17296 is False
20816 is True
11628 is True
17688 is True
12856 is True
19400 is True
8804 is True
16420 is True
13352 is True
11184 is True
3872 is True
8288 is True
17296 is True
20816 is True
11628 is True
17688 is True
12856 is True
19400 is True
Thread count: 12
Time elapsed: 3
cls;
$stopwatch = [system.diagnostics.stopwatch]::StartNew();
#region Runspace Pool
[runspacefactory]::CreateRunspacePool() | Out-Null;
$SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault();
$RunspacePool = [runspacefactory]::CreateRunspacePool(
1, #Min Runspaces
16 #Max Runspaces
);
$RunspacePool.Open();
#endregion Runspace pool
$threads = New-Object System.Collections.ArrayList;
$servers = #("goodServer1", "goodServer2", "badServer1", "goodServer3");
foreach ($server in $servers)
{
$PowerShell = [powershell]::Create();
$PowerShell.RunspacePool = $RunspacePool;
[void]$PowerShell.AddScript({
Param ($server, $portNumber)
[pscustomobject]#{
server = $server
portNumber = $portNumber
} | Out-Null
Function testPort ($server, $portNumber)
{
$testPort = New-Object System.Net.Sockets.TCPClient # -ArgumentList $server, 3389;
$testPort.SendTimeout = 3;
try
{
$testPort.Connect($server, $portNumber);
}
catch
{
#do nothing;
}
$result = $testPort.Connected;
$testPort.Close();
$dateTime = ([DateTime]::Now.ToString());
return "$server|testPort|$result|$dateTime"; # server | function | result | DateTime
}
testPort -server $server -portNumber $portNumber;
}) # end of add script
$portNumber = "3389";
$PowerShell.AddParameter('server', $server).AddParameter('portNumber', $portNumber) | Out-Null;
$returnVal = $PowerShell.BeginInvoke();
$temp = "" | Select PowerShell,returnVal;
$temp.PowerShell = $PowerShell;
$temp.returnVal = $returnVal;
$threads.Add($Temp) | Out-Null;
$PowerShell = [powershell]::Create();
$PowerShell.RunspacePool = $RunspacePool;
[void]$PowerShell.AddScript({
Param ($server, $shareName, $timeOutInMs)
[pscustomobject]#{
server = $server
shareName = $shareName
timeOutInMs = $timeOutInMs
} | Out-Null
Function testShare ($server, $shareName, $timeOutInMs)
{
$cSharp =
#'
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace cSharp7
{
public class cSharpClass
{
public bool verifyDirectoryExists(string uri, int timeoutInMs)
{
var task = new Task<bool>(() =>
{
var dir = new DirectoryInfo(uri);
return dir.Exists;
});
task.Start();
return task.Wait(timeoutInMs) && task.Result;
}
public bool verifyFileExists(string uri, int timeoutInMs)
{
var task = new Task<bool>(() =>
{
var fi = new FileInfo(uri);
return fi.Exists;
});
task.Start();
return task.Wait(timeoutInMs) && task.Result;
}
}
}
'#
$assemblies = ("System", "System.Collections", "System.ComponentModel", "System.Data", "System.Drawing", "System.Linq", "System.Threading.Tasks", "System.Windows.Forms", "System.Management.Automation", "System.Security", "System.Threading", "System.Collections.Concurrent", "System.Security.Principal", "System.Management", "System.IO", "System.Collections");
Add-Type -TypeDefinition $cSharp -ReferencedAssemblies $assemblies -Language CSharp;
$directoryExists = New-Object CSharp7.cSharpClass;
$path = "\\" + $server + "\" + $shareName;
try
{
$result = $directoryExists.verifyDirectoryExists($path, $timeOutInMs); # has a 2 minute timeout period, needs an asynchronous thread with a timeout period
#Write-Host $result;
}
catch
{
# do nothing
}
$dateTime = ([DateTime]::Now.ToString());
return "$server|testShare|$result|$dateTime"; # server | function | result | DateTime
}
testShare -server $server -shareName $shareName -timeOutInMs $timeOutInMs;
}) # end of add script
$shareName = "c$";
$timeOutInMs = "3000";
$PowerShell.AddParameter('server', $server).AddParameter('shareName', $shareName).AddParameter('timeOutInMs', $timeOutInMs) | Out-Null;
$returnVal = $PowerShell.BeginInvoke();
$temp = "" | Select PowerShell,returnVal;
$temp.PowerShell = $PowerShell;
$temp.returnVal = $returnVal;
$threads.Add($Temp) | Out-Null;
$PowerShell = [powershell]::Create();
$PowerShell.RunspacePool = $RunspacePool;
[void]$PowerShell.AddScript({
Param ($server, $pingCount)
[pscustomobject]#{
server = $server
pingCount = $pingCount
} | Out-Null
Function testPing ($server, $pingCount)
{
try
{
$result = Test-Connection $server -Count $pingCount -Quiet;
}
catch
{
# do nothing
}
$dateTime = ([DateTime]::Now.ToString());
return "$server|testPing|$result|$dateTime"; # server | function | result | DateTime
}
testPing -server $server -pingCount $pingCount;
}) # end of add script
$pingCount = "1";
$PowerShell.AddParameter('server', $server).AddParameter('pingCount', $pingCount) | Out-Null;
$returnVal = $PowerShell.BeginInvoke();
$temp = "" | Select PowerShell,returnVal;
$temp.PowerShell = $PowerShell;
$temp.returnVal = $returnVal;
$threads.Add($Temp) | Out-Null;
}
$completed = $false;
while ($completed -eq $false)
{
$completed = $true;
foreach ($thread in $threads)
{
$endInvoke = $thread.PowerShell.EndInvoke($thread.returnVal);
$endInvoke;
$threadHandle = $thread.returnVal.AsyncWaitHandle.Handle;
$threadIsCompleted = $thread.returnVal.IsCompleted;
#Write-Host "$threadHandle is $threadIsCompleted";
if ($threadIsCompleted -eq $false)
{
$completed = $false;
}
}
Write-Host "";
sleep -Milliseconds 500;
}
foreach ($thread in $threads)
{
$thread.PowerShell.Dispose();
}
$stopwatch.Stop();
Write-Host "";
Write-Host "Thread count:" $threads.Count;
Write-Host "Time elapsed:" $stopwatch.Elapsed.Seconds;
Here is how you capture the return value data. You define the custom object $temp with 2 properties names, in thise case Powershell and returnVal. Then you add them to an array list. After the async BeginInvoke is completed you can call EndInvoke against the asyncResult by doing this $endInvoke = $thread.PowerShell.EndInvoke($thread.returnVal); and parse it however you want.
This has to be the most complicated script I've ever written with a runspace pool, runspaces, asynchronous returns, functions being passed and even some c# mixed in. Hopefully others can draw from parts or all of this.
$returnVal = $PowerShell.BeginInvoke();
$temp = "" | Select PowerShell,returnVal;
$temp.PowerShell = $PowerShell;
$temp.returnVal = $returnVal;
$threads.Add($Temp) | Out-Null;
$completed = $false;
while ($completed -eq $false)
{
$completed = $true;
foreach ($thread in $threads)
{
$endInvoke = $thread.PowerShell.EndInvoke($thread.returnVal);
$endInvoke;
$threadHandle = $thread.returnVal.AsyncWaitHandle.Handle;
$threadIsCompleted = $thread.returnVal.IsCompleted;
#Write-Host "$threadHandle is $threadIsCompleted";
if ($threadIsCompleted -eq $false)
{
$completed = $false;
}
}
Write-Host "";
sleep -Milliseconds 500;
}
foreach ($thread in $threads)
{
$thread.PowerShell.Dispose();
}
I'm building a script which will go to each DC's and take the value from modifyTimeStampe values and then on take the maximum value as the following code, at the moment the code is too slow because now it goes to each domain and put the value to the array, then get the max value from the array out. I would like to speed it up. I was thinking to use multiple threads, but still looking for a better idea to implement.
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.PageSize = 100
$Searcher.SearchScope = "subtree"
$Searcher.Filter = "(&(objectCategory=$objectCategory)(objectClass=$objectClass))"
$Searcher.PropertiesToLoad.Add("distinguishedName")|Out-Null
$Searcher.PropertiesToLoad.Add("modifyTimeStamp")|Out-Null
Function modiScan{
forEach ($users In $userObjects)
{
$DN = $users.Properties.Item("distinguishedName")[0]
$dnarr.add($DN)|Out-Null
}
#$dnarr
foreach($dnn in $dnarr){
$error = $false
$lastmd = New-Object System.Collections.ArrayList
ForEach ($DC In $Domain.DomainControllers){
$Server = $DC.Name
$Base = "LDAP://$Server/"+$dnn
$Searcher.SearchRoot = $Base
try{
$Results2 = $Searcher.FindAll()
ForEach ($Result2 In $Results2)
{
$DN2 = $Result2.Properties.Item("distinguishedName")[0]
if($DN2 -eq $dnn){
$modi = $Result2.Properties.Item("modifyTimeStamp")[0]
if($modi){
$lastmd.Add($modi)|Out-Null
}
}
}
}
catch{
$error = $true
}
}
if($error -eq $true){
$lastModi = "None-set"
$global:noneModi++
}
else{
$lastModi = ($lastmd |measure -max).maximum
if($lastModi -ne $null){
$lastModi = $lastModi.ToString("yyyy/MM/dd")
if($lastModi.split("/")[0] -eq 2015){
$global:modi2015++
}
elseif($lastModi.split("/")[0] -eq 2016){
$global:modi2016++
}
elseif($lastModi.split("/")[0] -eq 2017){
$global:modi2017++
}
else{
$global:otherModi++
}
}
else{
$lastModi = "N/A"
$global:noneModi++
}
}
#$lastModi
$obj = New-Object -TypeName psobject
$obj | Add-Member -MemberType NoteProperty -Name "modi" -Value $lastModi
$obj | Export-Csv -Path "$outFileModi" -NoTypeInformation -append -Delimiter $Delimiter
}
}
modiScan
I've stumbled upon some (DCOM) issues when using the Excel.Application object. So I've rewritten my function to read Excel files by using the OleDbConnectionand OleDbCommand objects available within AccessDatabaseEngine_x64. So there's no need to install MS Office on the server anymore and it's also more reliable I think.
It works great but the only thing missing is that I can't seem to figure out how to remove empty lines from the output of the function. I know it can be done by using $DataTable[0].Delete(), but I don't know how to determine if a complete row is empty. As it can vary in size depending on the input.
XLSX-File example:
Name | Sir name
Bob Lee | Swagger
|
Jake | Thornton
In the example above I would like to have the output only 2 (or 3 lines depending on the $Header switch), but I don't want to see the blank line in the output.
I found a solution in another language, but I can't translate it to PowerShell.
The code:
Function Import-Excel {
[CmdletBinding()]
Param (
[parameter(Mandatory=$true,Position=0)]
[ValidateScript({Test-Path $_ -PathType Leaf})]
[String]$FileName,
[parameter(Mandatory=$true,Position=1)]
[String]$WorksheetName,
[Switch]$Header
)
Begin {
# We can't read open XLSX-Files
Copy-Item -LiteralPath $FileName -Destination $env:TEMP
}
Process {
$OleDbConnection = New-Object 'System.Data.OleDb.OleDbConnection'
$OleDbCommand = New-Object 'System.Data.OleDb.OleDbCommand'
$ConnString = 'Provider=Microsoft.ACE.OLEDB.12.0;Data Source='
$ConnString += "$env:TEMP\$(Split-Path $FileName -Leaf)"
if ($header) {
$ConnString += ';Extended Properties="Excel 12.0;HDR=YES;IMEX=1";'
}
else {
$ConnString += ';Extended Properties="Excel 12.0;HDR=NO;IMEX=1";'
}
$OleDbConnection.ConnectionString = $ConnString
$OleDbConnection.Open()
$OleDbCommand.Connection = $OleDbConnection
$OleDbCommand.CommandText = "SELECT * FROM [$WorksheetName$]"
$OleDbAdapter = New-Object "System.Data.OleDb.OleDbDataAdapter"
$OleDbAdapter.SelectCommand = $OleDbCommand
$DataTable = New-Object "System.Data.DataTable"
$OleDbAdapter.Fill($DataTable)
$OleDbConnection.Close()
Write-Output $DataTable
}
End {
Remove-Item "$env:TEMP\$(Split-Path $FileName -Leaf)"
}
}
Thank you for your help.
Found the answer in the meantime, for anyone encountering the same issues:
Function Import-Excel {
[CmdletBinding()]
Param (
[parameter(Mandatory=$true,Position=0)]
[ValidateScript({Test-Path -LiteralPath $_ -PathType Leaf})]
[String]$FileName,
[parameter(Mandatory=$true,Position=1)]
[String]$WorksheetName,
[Switch]$Header
)
Begin {
# We can't read open XLSX-Files
Copy-Item -LiteralPath $FileName -Destination $env:TEMP
}
Process {
$OleDbConnection = New-Object 'System.Data.OleDb.OleDbConnection'
$OleDbCommand = New-Object 'System.Data.OleDb.OleDbCommand'
$OleDbAdapter = New-Object 'System.Data.OleDb.OleDbDataAdapter'
$DataTable = New-Object 'System.Data.DataTable'
$ConnString = 'Provider=Microsoft.ACE.OLEDB.12.0;Data Source='
$ConnString += "$env:TEMP\$(Split-Path $FileName -Leaf)"
if ($header) {
$ConnString += ';Extended Properties="Excel 12.0;HDR=YES;IMEX=1";'
}
else {
$ConnString += ';Extended Properties="Excel 12.0;HDR=NO;IMEX=1";'
}
$OleDbConnection.ConnectionString = $ConnString
$OleDbConnection.Open()
$OleDbCommand.Connection = $OleDbConnection
$OleDbCommand.CommandText = "SELECT * FROM [$WorksheetName$]"
$OleDbAdapter.SelectCommand = $OleDbCommand
$OleDbAdapter.Fill($DataTable)
$OleDbConnection.Close()
# Remove empty lines
$Columns = $DataTable.Columns.Count
$Rows = $DataTable.Rows.Count
for ($r = 0; $r -lt $Rows; $r++) {
$Empty = 0
for ($c = 0; $c -lt $Columns; $c++) {
if ($DataTable.Rows[$r].IsNull($c)) {
$Empty++
}
}
if ($Empty -eq $Columns) {
# Mark row for deletion:
$DataTable.Rows[$r].Delete()
}
}
# Delete marked rows:
$DataTable.AcceptChanges()
Write-Output $DataTable
}
End {
Remove-Item "$env:TEMP\$(Split-Path $FileName -Leaf)"
}
}
I am having the following syntax problem, and I am still unsure if this is possible or not.
An empty pipe element is not allowed.
The script is
$web = get-spweb https://xx.com/sites/billing
$list = $web.Lists["Bill Cycles"]
foreach($wf in $list.WorkflowAssociations)
{
if ($wf.Name -like "*2013*")
{
foreach($listitem in $list.Items)
{
foreach($Workflow in $listitem.Workflows)
{
if($wf.InternalStatus -ne "Completed")
{
if($Workflow.AssociationId -eq $wf.Id)
{
New-Object psobject -Property #{
"InternalStatus" = $wf.InternalStatus
"WFName" = $wf.Name
"ListItemName" = $listitem.Name
"Url" = $listitem.Url
"Days" = ((Get-Date) - $Workflow.Created).Days
}
}
}
}
}
}
} | Select-Object InternalStatus, WFName, ListItemName, Url, Days | Export-CSV $output -Delimiter ',' -NoTypeInformation
You can make a foreach loop output to the pipeiline by using a sub-expression:
$web = get-spweb https://xx.com/sites/billing
$list = $web.Lists["Bill Cycles"]
$(foreach($wf in $list.WorkflowAssociations)
{
if ($wf.Name -like "*2013*")
{
foreach($listitem in $list.Items)
{
foreach($Workflow in $listitem.Workflows)
{
if($wf.InternalStatus -ne "Completed")
{
if($Workflow.AssociationId -eq $wf.Id)
{
New-Object psobject -Property #{
"InternalStatus" = $wf.InternalStatus
"WFName" = $wf.Name
"ListItemName" = $listitem.Name
"Url" = $listitem.Url
"Days" = ((Get-Date) - $Workflow.Created).Days
}
}
}
}
}
}
}) | Select-Object InternalStatus, WFName, ListItemName, Url, Days | Export-CSV $output -Delimiter ',' -NoTypeInformation
You can also use a scriptblock invocation (wrap the loop in &{} instead of $() )
A foreach loop doesn't output to the pipeline, so it's likely Export-CSV hasn't got any input.
Try wrapping the iteration of WorkflowAssociations in parenthesis.