Start-Job -ScriptBlock: Retrieving an array that is compiled inside the ScriptBlock - arrays

I have a Foreach loop inside a ScriptBlock that builds an array.
I cannot figure out how to retrieve the array from the Job once it's finished.
Here is my current code.
$HSMissingEmail = New-Object System.Collections.ArrayList
$HSDataObjects = New-Object System.Collections.ArrayList
$HSMissingEmail = Start-Job -Name HSMissingEmailStatus -ScriptBlock {
param($HSDataObjects, $HSMissingEmail);
foreach ($HSDO in $HSDataObjects) {
$HSDO = $HSDO | Select-Object Name, Location, Telephone, EmailAddress, Comments;
if ($HSDO | Where-Object {$_.EmailAddress -like ""}) {
$HSMissingEmail.Add($HSDO)
}
}
} -HSDataObjects $HSDataObjects -HSMissingEmail $HSMissingEmail | Receive-Job -Name HSMissingEmailStatus
I've also tried the following but it didn't do anything either.
$HSMissingEmail = New-Object System.Collections.ArrayList
$HSDataObjects = New-Object System.Collections.ArrayList
$ScriptBlock =
{
param($HSDataObjects,$HSMissingEmail)
foreach ($HSDO in $HSDataObjects)
{
$HSDO = $HSDO | Select-Object Name, Location, Telephone, EmailAddress, Comments
if ($HSDO | Where-Object {$_.emailaddress -like ""})
{
$HSMissingEmail.Add($HSDO)
}
}
}
Start-Job -Name HSMissingEmailStatus -ScriptBlock $ScriptBlock -HSDataObjects $HSDataObjects -HSMissingEmail $HSMissingEmail
ProgressBar ([REF]$HSMissingEmailStatus)
$HSMissingEmail = Receive-Job -Name HSMissingEmailStatus
Get-job -Name HSMissingEmailStatus | Remove-Job
I have tried many different ways to form the ScriptBlock but none are returning anything to $HSMissingEmail.
Also the second block of code doesn't get the passed data until I make the ScriptBlock all one line, which I'm unsure if this is a default behavior.
How can I retrieve the array?

You need to write the array out to standard out.
$HSMissingEmail = New-Object System.Collections.ArrayList
$HSDataObjects = New-Object System.Collections.ArrayList
$HSMissingEmail = Start-Job -Name HSMissingEmailStatus -ScriptBlock {
param($HSDataObjects, $HSMissingEmail);
foreach ($HSDO in $HSDataObjects) {
$HSDO = $HSDO | Select-Object Name, Location, Telephone, EmailAddress, Comments;
if ($HSDO | Where-Object {$_.EmailAddress -like ""}) {
$HSMissingEmail.Add($HSDO)
}
}
$HSMissingEmail # Drops it out as a result of the script block
} -HSDataObjects $HSDataObjects -HSMissingEmail $HSMissingEmail
Receive-Job -Name HSMissingEmailStatus -Wait # At the appropriate time, or keep cycling until you get it all
As far as the single-line/multiline bit, it might be a problem with not explicitly typing the variable as [ScriptBlock], but I'd have to check.

Related

Need Help Understanding Runspaces with WPF

I'm new to any sort of programming at all and I've been working on this tool, but I've finally hit a wall. I've read every article, post, and watched every video I could find but runspaces just aren't making sense to me. All I'm trying to do is get the auto search function to run on separate thread to keep the main window from locking up. I've been stuck on this for so long now that I considered ditching Powershell to start learning Python, but I'd rather understand this than quit. I think I even understand how to set the function to run on a separate runspace but the specific part that I don't understand is how to run the UI in it's own runspace and have the two communicate. I've even tried using some boilerplate with a syncHash table but I could never get it working. I'd really appreciate any help before I go insane.
All Files:
https://mega.nz/folder/251yHaBJ#bHYNYdNfAmia5mEhE5IOKQ
Powershell script in question:
#Xaml import
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$xamlFile = "MainWindow.xaml"
$inputXAML = Get-Content -Path $xamlFile -Raw
$inputXAML = $inputXAML -replace 'mc:Ignorable="d"','' -replace "x:N","N" -replace '^<Win.*','<Window'
[XML]$XAML = $inputXAML
$reader = New-Object System.Xml.XmlNodeReader $XAML
try {
$psform = [Windows.Markup.XamlReader]::Load($reader)
}
catch {
Write-Host $_.Exception
throw
}
$xaml.SelectNodes("//*[#Name]") | ForEach-Object {
try {
Set-Variable -Name "var_$($_.Name)" -Value $psform.FindName($_.Name) -ErrorAction Stop
}
catch {
throw
}
}
#Functions
function autoSearch {
$Drives = Get-PSDrive -PSProvider "FileSystem"
$a1 = foreach ($Drive in $Drives) {Get-ChildItem -Path $Drive.Root -Recurse -ErrorAction SilentlyContinue -Directory -Filter "Steam"}
$a2 = Get-ChildItem (Join-Path $a1.FullName "userdata") -ErrorAction SilentlyContinue -Directory
$a3 = Get-ChildItem (Join-Path $a2.FullName "*") -ErrorAction SilentlyContinue -Directory -Filter "1446780"
if ($a3) {
$var_saveR.Foreground = "Green"
$var_saveR.Content = "Found"
$timer = New-Object System.Windows.Forms.Timer -Property #{
Enabled = $true
Interval = 3000
}
$timer.add_Tick({$var_saveR.Content = ""})
$var_loc1.Content = $a3
$filePath = "Config.txt"
$lineNumber = "1"
$fileContent = Get-Content $filePath
$fileContent[$lineNumber-1] = $a3.FullName + "\*"
$fileContent | Set-Content $filePath -Force
}
else {
$var_saveR.Foreground = "Red"
$var_saveR.Content = "Not Found"
$timer = New-Object System.Windows.Forms.Timer -Property #{
Enabled = $true
Interval = 7000
}
$timer.add_Tick({$var_saveR.Content = ""})
}
}
function manualSave {
Add-Type -AssemblyName System.Windows.Forms
$browser = New-Object System.Windows.Forms.FolderBrowserDialog
$browser.ShowDialog()
$var_loc1.Content = $browser.SelectedPath
$filePath = "Config.txt"
$lineNumber = "1"
$fileContent = Get-Content $filePath
$fileContent[$lineNumber-1] = $var_loc1.Content + "\*"
$fileContent | Set-Content $filePath -Force
}
function manualBack {
Add-Type -AssemblyName System.Windows.Forms
$browser = New-Object System.Windows.Forms.FolderBrowserDialog
$browser.ShowDialog()
$var_loc2.Content = $browser.SelectedPath
$filePath = "Config.txt"
$lineNumber = "2"
$fileContent = Get-Content $filePath
$fileContent[$lineNumber-1] = $var_loc2.Content + "\*"
$fileContent | Set-Content $filePath -Force
}
function backup {
$file = "Config.txt"
$a1 = Get-Content $file
$a1[0]
$a1[1]
Copy-Item $a1[0] ($a1[1] -replace '\\\*','') -Recurse
$var_backupStatus.Foreground = "Green"
$var_backupStatus.Content = "Done"
$timer = New-Object System.Windows.Forms.Timer -Property #{
Enabled = $true
Interval = 5000
}
$timer.add_Tick({$var_backupStatus.Content = ""})
}
function restore {
$file = "Config.txt"
$a1 = Get-Content $file
$a1[0]
$a1[1]
Copy-Item $a1[1] ($a1[0] -replace '\\\*','') -Recurse #-Confirm
$var_restoreStatus.Foreground = "Green"
$var_restoreStatus.Content = "Done"
$timer = New-Object System.Windows.Forms.Timer -Property #{
Enabled = $true
Interval = 5000
}
$timer.add_Tick({$var_restoreStatus.Content = ""})
}
#UI
$var_aSearchButton.Add_Click({autoSearch})
$var_mSetButton1.Add_Click({manualSave})
$var_mSetButton2.Add_Click({manualBack})
$var_backupButton.Add_Click({backup})
$var_restoreButton.Add_Click({restore})
$var_loc1.Content = (Get-Content "Config.txt" -TotalCount 1) -replace '\\\*',''
$var_loc2.Content = (Get-Content "Config.txt" -TotalCount 2)[-1] -replace '\\\*',''
$psform.ShowDialog() | Out-Null

Multiple varibales from a function into an array in Powershell

I have a function that returns several things and I need to store them into an array seperately.
The code I currently have is like so:
Function ADlocation{
Try{
$ADDetails = Get-ADComputer - Identity $Servername -Properties Description,LastLogOnTimeStamp -ErrorAction SilentlyContinue -ErrorVariable ADFail
}
Catch [Exception]{
return "$($Servername) not in AD"
}
If(!ADFail){
return (Get-ADOrganizationalUnit -Identity $(ADDetails.DistinguishedName.Replace("CN=$($ADDetails.Name),","")) -Properties canonicalName).canonicalName
return $ADDetails.Description
return ([datetime]::FromFileTime($ADDetails.LastLogonTimeStamp)).ToString()
}
}
$Output = #()
foreach ($ipAddress in $iplist){
$Servername = [System.Net.Dns]::GetHostByAddress($ipAddress).Hostname
if(Test-Connection $ipAddress -Quiet){
$Output += [PSCustomObject]#{
ip = $ipAddress
Name = $ServerName
Pingable = "Yes"
ADLocation = ADlocation
AdDescription = ADlocation
LAstLogOnTime = ADlocation
}
} else {
$Output +=[PSCustomObject]#{
ip = $ipAddress
Name = "N/A"
Pingable = "No"
}
}
}
$Output | Export-Csv -path $OutputPath -NoTypeInformation
I am unsure what i should call to specifically get the "ADlocation", "ADDescription" and LastLogOnTime
There are a couple of things amiss in your code. As commented, the three return statements in the function. In fact, you don't really need a helper function for this..
Also, there is a syntax error on - Identity $Servername, where the space should not be there between the hyphen and the parameter name Identity.
Then, if you want to output a valid CSV, you need to specify the same objects with the same properties, both when succeeded and when failed.
I think the easiest way to do this, is to merge success/failed like below:
Assuming your $iplist variable is an array of IP addresses
$OutputPath = 'D:\Test\computers.csv' # enter the path and filename you want here
# loop over the IP addresses in the list
$Output = foreach ($ipAddress in $iplist) {
# initialize some variables
$pingable = 'No'
$Servername, $ADDetails = $null
if (Test-Connection -ComputerName $ipAddress -Quiet -Count 1) {
$pingable = 'Yes'
# GetHostByAddress is obsolete, use GetHostEntry
$Servername = [System.Net.Dns]::GetHostEntry($ipAddress).Hostname
# rather use Filter than Identity so exceptions can be silenced with -ErrorAction SilentlyContinue
$ADDetails = Get-ADComputer -Filter "Name -eq '$Servername'" -Properties Description,LastLogOnDate, CanonicalName -ErrorAction SilentlyContinue
}
# simply output an object to be collected in variable $Output
[PSCustomObject]#{
IP = $ipAddress
Name = if ([string]::IsNullOrWhiteSpace($ServerName)) { 'N/A' } else { $ServerName }
Pingable = $pingable
ADLocation = if ($ADDetails) { Split-Path -Path $ADDetails.CanonicalName -Parent } else { 'N/A' }
ADDescription = if ($ADDetails) { $ADDetails.Description } else { 'N/A' }
LastLogOnDate = if ($ADDetails) { $ADDetails.LastLogOnDate } else { 'N/A' }
}
}
# output on screen
$Output | Format-Table -AutoSize
# output to CSV file
$Output | Export-Csv -Path $OutputPath -NoTypeInformation

Using ref in powershell to return values from function

I've function DoWork which creates an object and keeps it in $AllMailboxes variable. Then within that function I execute another function ProcessEmail which is supposed to take $Mailbox out of $AllMailboxes and variable by ref, add couple of fields to it and either update $AllMailboxes or create new $collection which then holds all $Mailbox with updated fields
$collection = #()
function DoWork() {
Get-User -ResultSize Unlimited | Where { $_.RecipientType -eq 'UserMailbox' } | ForEach { $Users = #{} } { $Users[$_.SamAccountName] = $_ }
$AllMailboxes = Get-Mailbox -ResultSize Unlimited | Where { $_.RecipientTypeDetails -eq "UserMailbox" } | ForEach {
$PrimarySmtpDomain = $_.PrimarySmtpAddress.split("#")
New-Object psobject |
Add-Member -PassThru NoteProperty Alias $_.Alias |
Add-Member -PassThru NoteProperty Name $_.Name |
Add-Member -PassThru NoteProperty DisplayName $_.DisplayName
Add-Member -PassThru NoteProperty .... other values
foreach ($mailbox in $allmailboxes) {
$FullEmail = "somestring"
ProcessEmail ([ref] $Mailbox) ($FullEmail)
}
$collection | ft # doesn't display anything
}
function ProcessEmail ([ref] $Mailbox, $FullEmail) {
$RequireAdd = $true
$addresses = $Mailbox.EmailAddresses
foreach ($address in $addresses) {
if ($address -imatch "sip:") { continue }
if ($address -ireplace("smtp:","") -ieq $FullEmail) {
$requireAdd = $false
break
}
$Mailbox | Add-Member -MemberType NoteProperty -Name NewEmailToAdd -Value $FullEmail
$Mailbox | Add-Member -MemberType NoteProperty -Name NewEmailRequiresAdding -Value $RequireAdd
$Mailbox.NewEmailToAdd # displays correctly
$Mailbox.NewEmailRequiresAdding #display correctly
$collection += $Mailbox
}
I've tried multiple approces with ref, without ref, creating separate variables but I can't for some reason make it to display anything in $collection or in other means outsied of ProcessEmail function. I'm sure I'm missing something.
You're making it more complex by using PSReference (which would need you to access the value property). You have no need to here so far.
There's also little need to use that global / script variable except perhaps as an assignment from DoWork as shown in this mock up.
function DoWork {
foreach ($i in (1..100)) {
$psObject = [PSCustomObject]#{
Property1 = 1
Property2 = 2
}
ProcessEmail -Mailbox $psObject -FullEmail $FullEmail
$psObject
}
}
function ProcessEmail {
param(
$Mailbox,
)
$Mailbox | Add-Member NewProperty1 "one"
$Mailbox | Add-Member NewProperty2 "two"
}
$collection = DoWork
Chris
Seems like you are missing scope. Change it to at least script scope, like this:
$script:collection = #()
$script:collection += $Mailbox
I've actually decided to go
function ProcessEmail ($Mailbox, $FullEmail) {
$RequireAdd = $true
$addresses = $Mailbox.EmailAddresses
foreach ($address in $addresses) {
if ($address -imatch "sip:") { continue }
if ($address -ireplace("smtp:","") -ieq $FullEmail) {
$requireAdd = $false
break
}
}
$Mailbox | Add-Member -MemberType NoteProperty -Name NewEmailToAdd -Value $FullEmail
$Mailbox | Add-Member -MemberType NoteProperty -Name NewEmailRequiresAdding -Value $RequireAdd
return ,$mailbox
}
And simply go by:
$Mailbox = ProcessEmail ($Mailbox) ($FullEmail)
$collection += $Mailbox
Seems to work just fine.

Array removed after function?

I am making a script that goes into all servers we're hosting and gets all members of a specific group and the domain name, and then exports it to a file. I'm saving the users and the domain names into two arrays AA (user array) and DA (domain array) AA stands for användararray, and "användare" is users in swedish so it makes sense to me.
I noticed that the export step didn't work, no users or domain names were exported, so I tried to print them in the function. But it doesn't print anything, so I tried to print it in a different location (didn't work). After some experimenting I came to the conlusion that the only place the arrays actually contains any information is inside the foreach loop where I save the users that I find??!
Here is the code
unction GetData([int]$p) {
Write-Host("B")
for ($row = 1; $row -le $UsernamesArray.Length; $row++)
{
if($CloudArray[$row] -eq 1)
{
.
$secstr = New-Object -TypeName System.Security.SecureString
$PasswordsArray[$row].ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $UsernamesArray[$row], $secstr
$output = Invoke-Command -computername $AddressArray[$row] -credential $cred -ScriptBlock {
Import-Module Activedirectory
foreach ($Anvandare in (Get-ADGroupMember fjärrskrivbordsanvändare))
{
$AA = #($Anvandare.Name)
$DA = gc env:UserDomain
#$DA + ";" + $Anvandare.Name
$DA + ";" + $AA
}
}
$output
}
}
$DA
$AA
}
function Export {
Write-Host("C")
$filsökväg = "C:\Users\322sien\Desktop\Coolkids.csv"
$ColForetag = "Företag"
$ColAnvandare = "Användare"
$Emptyline = "`n"
$delimiter = ";"
for ($p = 1; $p -le $DomainArray.Length; $p++) {
$ColForetag + $delimiter + $ColAnvandare | Out-File $filsökväg
$DA + $delimiter + $AA | Out-File $filsökväg -Append
}
}
ReadInfo
GetData
Export
Can anyone help me with this? I've sat down with this all day and i cant find a solution.
Your variables $DA and $AA are bound to GetData function, so they live only there. You could make them available inside your script by changing it's scope.
Change this:
$AA = #($Anvandare.Name)
$DA = gc env:UserDomain
To this:
$script:AA = #($Anvandare.Name)
$script:DA = gc env:UserDomain
So they will now be available for other functions inside the script.
Also I found the ways to improve your script, hope you can see the logic:
function GetData([int]$p) {
Write-Host("B")
for ($row = 1; $row -le $UsernamesArray.Length; $row++)
{
if($CloudArray[$row] -eq 1)
{
.
$secstr = New-Object -TypeName System.Security.SecureString
$PasswordsArray[$row].ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $UsernamesArray[$row], $secstr
[array]$output = Invoke-Command -computername $AddressArray[$row] -credential $cred -ScriptBlock {
Import-Module Activedirectory
$array = #()
foreach ($Anvandare in (Get-ADGroupMember fjärrskrivbordsanvändare))
{
$object = New-Object PSObject
$object | Add-Member -MemberType NoteProperty -Name AA -Value #($Anvandare.Name)
$object | Add-Member -MemberType NoteProperty -Name DA -Value (gc env:UserDomain)
$object | Add-Member -MemberType NoteProperty -Name Something -Value $DA + ";" + $AA
$array += $object
}
Write-Output $array
}
Write-Output $output
}
}
}
Your function will now output some data.

Powershell Group Array

I have a custom array
$myresults = #()
$w3svcID = $result.ReturnValue -replace "IISWebServer=", ""
$w3svcID = $w3svcID -replace "'", ""
$vdirName = $w3svcID = "/ROOT";
$vdirs = gwmi -namespace "root\MicrosoftIISv2" -class "IISWebVirtualDirSetting"
foreach($vdir in $vdirs)
{
$vPool = $vdir.Apppoolid
$vName = $vdir.Name
$robj = New-Object System.Object
$robj | Add-Member -type NoteProperty -name Path -value $vName
$robj | Add-Member -type NoteProperty -name Pool -value $vPool
$myresults += $robj
}
$myresults | group-object Pool
I'd like to be able to Group the data in the form of a list where the group values (Path) is under the group-by values (Pool); like so:
DefaultAppPool
W3SVC\
W3VSC\1\ROOT\
MyAppPool
W3SVC\1\ROOT\MyVirtual\
Give this a try:
Get-WmiObject IISWebVirtualDirSetting -Namespace root\MicrosoftIISv2 |
Group-Object AppPoolId | Foreach-Object{
$_.Name
$_.Group | Foreach-Object { "`t$($_.Name)" }
}

Resources