Check a object-string against a array of exceptions - arrays

I got a bunch of objects containing multiple string values. I want to manage the exceptions with a object/array consisting of multiple more and less known exceptions. Is there a simple way to get this to work? afraid my brain ain't made for this kind of problemsolving.
Example:
Exceptions =#{
Known1 = 'Minor Error'
Known2 = 'This Have We Seen Before'
Known3 = 'I Remember This Issue'
Special1 = 'Application malfunction'
Special2 = 'Application malfunction severe!'
}
$AllCases = Get-Cases
Foreach ($Case in $AllCases){
if ($Case.Name -match $Exceptions.Values.('^Known+[0-9]$')){'Do That again'}
Elseif ($Case.Name -match $Exceptions.Values.Special1){'OBS! Do This Fast'}
Elseif ($Case.Name -match $Exceptions.Values.Special2){'SEVERE OBS! do this faster!'}
}
Thanks :)

I guess you're trying to create a script to identify incidents where $case.Name whatever that could be (subject of the incident?) matches with your map of words that are commonly found, which in turn points to a specific Error Code or Error Index? If that's the case, It is probably better even though slower, to store a separate CSV file where you can add new Error Indexes and make your script read this CSV each time. In any case, I harcoded an example of how the CSV could look like and added some comments to guide you with the tought process.
$Exceptions = #"
Index,Error
Known1,Minor Error
Known2,This Have We Seen Before
Known3,I Remember This Issue
Special1,Application malfunction
Special2,Application malfunction severe!
"# | ConvertFrom-Csv
# I'll generate 500 random cases here mixing the words on Error
# I'll also added a IncidentNumber property becase I'm guessing you have something like that
$words = $Exceptions.Error -split '\s'
$AllCases = 0..500 | ForEach-Object {
$ran = Get-Random -Minimum 2 -Maximum 10
[pscustomobject]#{
IncidentNumber = Get-Random
Name = ($words | Get-Random -Count $ran) -join ' '
}
}
Example of the cases created
PS /> $AllCases | Get-Random -Count 5
IncidentNumber Name
-------------- ----
2043166219 Application This Remember Before
837011116 malfunction Error Seen Have malfunction Issue Minor severe!
2103904733 This malfunction We This I Have Seen Application
323914959 Minor This Issue Remember This Application Have Seen
1105202359 malfunction Seen We This I malfunction
Back to the script:
$result = foreach($Case in $AllCases)
{
if($Exceptions.Error -match $Case.Name)
{
# Note: I'm using -match here instead of -in or -contains
# because we want to partially match, considering that $Case.Name
# could be a much longer string
# $Exceptions array where the string in $Case.Name
# matches any element of $Exceptions.Error
$Exceptions.Where({$_.Error -match $Case.Name}) |
Select-Object #{n='IncidentNumber';e={$Case.IncidentNumber}},Index,#{n='Status';e={'FOUND'}}
continue
}
# If there are no matches you can use something like below, not sure
# you have some sort of incident number or something short you can use
# for those incidents not found :)
$Case | Select-Object IncidentNumber,#{n='Index';e={$null}},#{n='Status';e={'NOT FOUND'}}
}
Since this is all random, it is possible there will not be any matches but in this run I got these results
PS /> $result | Sort-Object Status | Select-Object -First 10
IncidentNumber Index Status
-------------- ----- ------
505966803 Special1 FOUND
235217253 Known3 FOUND
1034830172 Known3 FOUND
1047600080 Special2 FOUND
481579809 Known3 FOUND
2131683661 Known2 FOUND
505966803 Special2 FOUND
1424263573 NOT FOUND
249127953 NOT FOUND
489126244 NOT FOUND

Related

Why is my ArrayList throwing a "PSCustomObject doesn't contain a method for 'foreach'" error when the ArrayList contains <= 1 item, & only in PS 5?

I have the following script I wrote using PowerShell 5 that utilizes the Active Directory and Join-Object PowerShell modules to get a list of all AD Groups and their users (along with some additional properties per user like their manager and title):
$ADGroupsList = #(Get-ADGroup -Filter * -Properties * | Select-Object DistinguishedName,CN,GroupCategory,Description | Sort-Object CN)
#I'm using an ArrayList here so that later on I can use the .Add() method to avoid costly += operations.
$ADUsersList = New-Object -TypeName "System.Collections.ArrayList"
$ADUsersList = [System.Collections.ArrayList]#()
$Record = [ordered] #{
"Group Name" = ""
"Employee Name" = ""
"Title"= ""
"Manager" = ""
}
foreach ($Group in $ADGroupsList) {
$ArrayofMembers = #(Get-ADGroupMember -Identity $Group.DistinguishedName | Where-Object { $_.objectClass -eq "user" })
#Loop through each member in the list of members from above
foreach ($Member in $ArrayofMembers) {
#Get detailed user info about the current user like title and manager that aren't available from Get-ADGroupMember
$User = #(Get-ADUser -Identity $Member -Properties name,title,manager | Select-Object Name, Title, #{Label="Manager";Expression={(Get-ADUser (Get-ADUser $Member -Properties Manager).Manager).Name}})
#Specifies what values to apply to each property of the $Record object
$Record."Group Name" = $Group.CN
$Record."Employee Name" = $Member.Name
$Record."Title" = $User.Title
$Record."Manager" = $User.Manager
#Put all the stored information above in a 'copy' record
$objRecord = New-Object PSObject -property $Record
#Add that copy to the existing data in the ADUsersList object
[void]$ADUsersList.Add($objRecord)
}
#Using Join-Object here to enable me to use SQL-like JOINs
Join-Object -Left $ADUsersList -Right $ADGroupsList -LeftJoinProperty "Group Name" -RightJoinProperty "CN" -Type AllInLeft -LeftMultiMode DuplicateLines -RightMultiMode DuplicateLines -ExcludeRightProperties DistinguishedName | Export-Csv ("C:\ADReports\" + $Group.CN + " Report.csv") -NoTypeInformation
$ADUsersList.Clear()
}
Here's the output I expect (columns may be out of order, but column ordering isn't important):
My code works great for most groups, but for groups that have only one member (or none), I get an error:
Join-Object : Method invocation failed because [System.Management.Automation.PSCustomObject] does not contain a method named 'ForEach'.
At C:\GetADGroups&Users.ps1:54 char:5
+ Join-Object -Left $ADUsersList -Right $ADGroupsList -LeftJoinProp ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (ForEach:String) [Join-Object], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound,Join-Object
At first, I thought it was because I read arrays/arraylists with one entry get turned into scalars. But a knee-jerk wrapping of every object I can think of in #() didn't resolve the issue. In fact, if I wrap the $objRecord assignment (New-Object PSObject -property $Record) in #() to convert it to an array, it writes the Member Properties of $ADUsersList to the Join-Object line instead of the contents of $ADUsersList, resulting in this:
Is there somewhere I've missed an array/arraylist getting converted to a scalar? Why is the code above throwing an error for groups with <= 1 entries?
Compounding my curiosity, PowerShell 7 (possibly 6, too) doesn't seem to care about this issue; it doesn't throw the error at all (instead it just outputs the appropriate single-value/blank CSV). Normally I'd just wipe my hands and say PS 7 is required, but I'd like to get this working in PowerShell 5, or at least understand what is causing the issue.
Googling led me to several related articles & questions, including:
Method Invocation .Foreach failed, System.Object doesn't contain a method named 'foreach' this one's specific to PowerShell v2 (I'm running v5)
Method invocation failed because [System.Management.Automation.PSObject] doesn't contain a method named 'op_Addition' this one seems only tangentially related. Incidentally it's where I read that arrays with one item output as scalars, as I mentioned earlier.
It does appear that scalars lack the .ForEach() & .Where() methods in 5.1. The additional of the methods is probably just an enhancement newer version, certainly 7 not sure about 6. I'm sure that's documented somewhere.
I can't really test your code but it doesn't look like there's anywhere that could be flipping to a scalar. To help guarantee ArrayList collections through out you can type constrain the variables like [Collections.ArrayList]$Var = #() This may end up being more practical than hunting for an implementing #() throughout.
Something that stands out is the error seems to come from Join-Object I only found a single invocation of .ForEach() on line 820 of Join-Object.ps1 My guess is it's this line or similar elsewhere in the module combined with the 5.1 runtime environment.
If you can manually modify that to a traditional | ForEach-Object {...} might be telling. And/or you can wrap $result like #($Result) right before the .ForEach() is invoked.
Really interested to see what you come up with. I see you've already posted an issue with the author. Please post back if you get a reply. Thanks.

Cannot add items to Powershell array

I'm relatively new to Powershell but haven't been able to find an answer online.
I'm trying to get the number of emails per disabled user in exchange 2010, but also need to get the user's title form AD as the organization groups users by type using the Title attribute in AD
I've written the following but I'm unable to get the data I need, it just returns Length and numbers to the CSV file e.g.
"length"
"10"
"3"
"34"
If I leave $title out of the assignment of $Disabled+= the user's name and item count is added to the csv file, but I really need the title also. Can anyone point out where I'm going wrong.
Import-Module ActiveDirectory
$i=0
$disUsers = Get-ADUser -Filter * -SearchBase "ou=User Disabled Accounts,dc=test,dc=com" -Properties SamAccountName,Title
$Disabled = #()
$disUsers | Foreach-Object{
$sam = $_.SamAccountName
$title = $_.Title
$mailDetail=Get-MailboxStatistics $sam | Select -Property DisplayName,ItemCount
$Disabled += $title, $mailDetail
$i++;
}
$Disabled | Export-Csv -Path $env:userprofile\desktop\DisabledADUserTitlewithMailbox.csv -NoTypeInformation
Working with the code provided by Steve unfortunately gives the following errors
Exception calling "Add" with "2" argument(s): "Item has already been added. Key in dictionary: 'ADCDisabledMail' Key being added: 'ADCDisabledMail'" ...
Exception calling "Add" with "2" argument(s): "Key cannot be null. Parameter name: key"...
EDIT
With help from Steven I was able to get this working with the following
'Import-Module ActiveDirectory'
$i=0
$disUsers=Get-ADUser -Filter {mailNickName -like '*'} -SearchBase "ou=User Disabled Accounts,dc=test,dc=com" -Properties SamAccountName,Title
$dis2 = $disUsers.count
$DisabledUser = #()
$disUsers | Foreach-Object{
Write-Host "Processing record $i of $dis2"
$sam = $_.SamAccountName
$title = $_.Title
$mailDetail=Get-MailboxStatistics $sam | Select-Object DisplayName, #{ Name = 'Title'; Expression = {$title}}, ItemCount
$DisabledUser+= $mailDetail
$i++;
}
$DisabledUser | Export-Csv -Path $env:userprofile\desktop\DisabledADUserTitlewithMailbox.csv -NoTypeInformation
It sounds like what you are really trying to do is relate data to create a small report. You are dealing with data coming from different commands so you need a property to join on. In this case I would look at the LegacyExchangeDN AD attribute and the LegacyDN property returned by Get-MailboxStatistics. The code might look something like:
$DisabledUsers = #{}
Get-ADUser -SearchBase 'ou=User Disabled Accounts,dc=test,dc=com' -Filter * -Properties 'Title','legacyExchangeDN' |
ForEach-Object{ $DisabledUsers.Add( $_.legacyExchangeDN, $_ ) }
$DisabledUsers.Values.SamAccountName |
Get-MailboxStatistics |
Select-Object DisplayName, ItemCount, #{ Name = 'Title'; Expression = { $DisabledUsers[$_.LegacyDN].Title } }
This will output something like:
DisplayName ItemCount Title
----------- --------- -----
Mr. Smith 113576 Executives
If you would rather it go directly to a CSV file simply add the Export-CSV command after the Select-Object command, like below:
$DisabledUsers = #{}
Get-ADUser -SearchBase 'ou=User Disabled Accounts,dc=test,dc=com' -Filter * -Properties 'Title','legacyExchangeDN' |
ForEach-Object{ $DisabledUsers.Add( $_.legacyExchangeDN, $_ ) }
$DisabledUsers.Values.SamAccountName |
Get-MailboxStatistics |
Select-Object DisplayName, ItemCount, #{ Name = 'Title'; Expression = { $DisabledUsers[$_.LegacyDN].Title } } |
Export-CSV -Path $env:userprofile\desktop\DisabledADUserTitlewithMailbox.csv -NoTypeInformation
I would've used Get-User from the Exchange Management Shell, however it doesn't have the LegacyExchangeDN as a returned property. It does have SamAccountName, but using it would've forced me to bridge everything through Get-Mailbox. At any rate, this is a very common technique to use a hash table to reference related values in a different collection.
I'm sure some additional work will be needed to get the report just right.
An aside, try to avoid using the += operator to append arrays. The best way to get an array is to let PowerShell provide it as I did above. However, if you can't get around it the most common alternative is an ArrayList. Like most things there are several ways to go about it below is just 1 example.
# To create:
$ArrList = [Collections.ArrayList]#()
#To Add a value:
[Void]$ArrList.Add( 'ValueOrObjectHere' )
Note: Documentation / discussion of += and ArrayList's are easy to
find with the Google machine...
Update:
Addressing Errors Noted in most recent edit:
The first error is basically impossible. Forgive me but I must assume you made some mistake to generate this error. LegacyExchangeDN should always start with '/o=...' and they key cited by the error was 'ADCDisabledMail' . Also LegacyExchangeDNs are naturally unique in Active Directory, so there's almost no chance you'd have a duplicate. As such, I made no effort, and none is warranted, to prevent such an unlikely error.
Note: If you are repeatedly testing the code you have to recreate the
hash, $DisabledUsers = #{} else the hash will exist from the
previous run and duplicate key errors are a certainty...
The second error, they 'key cannot be null' might be due to non-mailbox enabled AD accounts in the referenced OU effectively causing the LegacyExchangeDN attribute to be null for those users. Hence, null key.... You can avoid that by modifying the filter to only return mail enabled users:
$disUsers = Get-ADUser -Filter { mailNickName -like '*' } -SearchBase "ou=User Disabled Accounts,dc=test,dc=com" -Properties SamAccountName,Title
Note: For reference, mailNickName is the alias propertry typically
returned with Get-Mailbox

Replace not working when Looping through an Array

Working on a project and I have a need to replace a 'path prefix' with a different prefix. Automating the copying of files and then I will do using those files locally. Rather than making new variables I figured I would re-purpose old ones I would not need after the copy is complete. Ran into an issue with doing a replace when looping through the Array. I was able to find a solution for this particular scenario but would like to understand why my loops were not working
No errors are shown to give me an indication why it is not working.
Any help on understanding why the replace isn't working when looping would be helpful
Sample code of how I am building paths
[string]$Root_Path = "\\Main_Blah_Path\"
[string]$Software = $Root_Path + "Software\"
[string]$Adobe_Reader = $Software + "Adobe_Reader\"
[string]$Firefox = $Software + "Firefox\"
[string]$Google_Chrome = $Software + "Google_Chrome\"
[System.Collections.ArrayList]$List_Of_Software = #(
$Adobe_Reader
$Firefox
$Google_Chrome
)
Example of the ways I have done the replacement. These work and will write the desired output to the console
foreach ($Path in $List_Of_Software) {
$Path -replace '\\\\Main_Blah_Path\\','C:\Folder\'
}
$List_Of_Software | ForEach-Object {$_ -replace '\\\\Main_Blah_Path\\','C:\Folder\'}
Example of failures I am having. I cannot replace and save the data into itself to do a replacement. I couldn't get .replace to work at all
foreach ($Path in $List_Of_Software) {
$Path = $Path -replace '\\\\Main_Blah_Path\\','C:\Folder\'
}
$List_Of_Software | ForEach-Object {$_ = $_ -replace '\\\\Main_Blah_Path\\','C:\Folder\'}
foreach ($Path in $List_Of_Software) {
$Path.Replace('\\\\Main_Blah_Path\\','C:\Folder\')
}
Solution I am using for my current scenario but I can foresee a few things in my future where this method may not be a viable option
$List_Of_Software = $List_Of_Software -replace '\\\\Main_Blah_Path\\','C:\Folder\'
You're almost there, you just need to assign the results of the replacement back to the variable when you're looping the array. Try:
$List_of_software = $List_Of_Software |
ForEach-Object {$_ -replace '\\\\Main_Blah_Path\\','C:\Folder\'}

Test-Connection $False will not convert to ArrayList

Currently working on making a new report that will be generated with PowerShell. Using PowerShell to build a HTML email. I have one other report working fine but ran into an unexpected issue on this one.
The below code is just s sample from the script I am still building. Still adding pieces to the script but testing it as I move forward. I added a Test-Connection to see if a computer was responding or not and lost the ability to build an array.
My final goal with this report is to import a list of names from a file and then loop over all of the computers to see if they are pinging and gather some information from them using Get-WMIObject, etc.
The below code will replicate the issue I am having but I am not sure how to solve it. I've narrowed down the issue to when Test-Connection returns 'False'. On line 26 I am filtering for just results that returned a 'False' on Test-Connection to save them into its own array so that I can use that array in a different part of my code to build the HTML table/HTML to send out the email.
Only the flipside, if I tell it to look for only 'True', it will save into the array without issue.
This is the error that PowerShell is giving when doing filtering by 'False'.
Cannot convert value "#{Computer_Name=Computer1; Ping_Status=False}" to type "System.Collections.ArrayList". Error: "Cannot convert the "#{Computer_Name=Computer1 Ping_Status=False}" value of type "Selected.System.Management.Automation.PSCustomObject" to type "System.Collections.ArrayList"."
Please let me know if there is any other information that I can provide. I've been stuck on this one for a while. Co-workers are even say this is a weird one.
Is there something unique about the way Test-Connection return a 'False'?
CLS
[string]$ErrorActionPreference = "Continue"
[System.Collections.ArrayList]$Names = #(
"Computer1"
"Computer2"
)
[System.Collections.ArrayList]$WMI_Array = #()
[System.Collections.ArrayList]$Ping_Status_False = #()
foreach ($Name in $Names) {
[bool]$Ping_Status = Test-Connection $Name -Count 1 -Quiet
$WMI_Array_Object = [PSCustomObject]#{
'Computer_Name' = $Name
'Ping_Status' = $Ping_Status
}
$WMI_Array.Add($WMI_Array_Object) | Out-Null
}
$WMI_Array | Format-Table
[System.Collections.ArrayList]$Ping_Status_False = $WMI_Array | Where-Object {$_.Ping_Status -eq $false} | Select-Object Computer_Name, Ping_Status
$Ping_Status_False
The problem is not Test-Connection but that this statement
$WMI_Array | Where-Object {$_.Ping_Status -eq $false} | Select-Object Computer_Name, Ping_Status
produces just a single result. Which is not an array, and can thus not be converted to an ArrayList. The behavior is identical when you filter for $_.PingStatus -eq $true with just a single matching object, so I suspect that you had either more than one successfully pinged host or none at all when you tested that condition and it didn't throw the same error.
You could mitigate the problem by wrapping the statement in the array subexpression operator:
[Collections.ArrayList]$Ping_Status_False = #($WMI_Array |
Where-Object {$_.Ping_Status -eq $false} |
Select-Object Computer_Name, Ping_Status)
Or, you could simply drop all the pointless type-casting from your code:
$ErrorActionPreference = "Continue"
$Names = 'Computer1', 'Computer2'
$WMI_Array = foreach ($Name in $Names) {
[PSCustomObject]#{
'Computer_Name' = $Name
'Ping_Status' = [bool](Test-Connection $Name -Count 1 -Quiet)
}
}
$WMI_Array | Where-Object { -not $_.Ping_Status }

Powershell - compare Active Directory usernames with e-mail address

I am currently trying to find all AD users that have been created using the model "firstname#domain.com" versus our new standard of "FirstInitialLastName#domain.com". I'm using the Quest ActiveRoles modules, specifically Get-QADUser to pull down my user details:
Get-QADUser -enabled -IncludedProperties PrimarySMTPAddress | ?{$_.Type -match "User"} | Select-Object FirstName,PrimarySMTPAddress ...
That gets me a list of user first names and their SMTP address. Where I am stumped is how to compare the results.
I thought normalizing the values (either adding "#domain.com" to the first name string or stripping "#domain.com" from the SMTP string) and then doing a -ieq test would be the best approach. I have found I can do the first with:
%{ $address=$($_.FirstName + "#domain.com";) }
But I can't figure out how to then test $address against the PrimarySMTPAddress string. I can create a second variable with:
%{ $smtp=$($_.PrimarySMTPAddress); }
and get the result:
[PS] C:\>$addy -ieq $smtp
True
I'm just unclear how to do it all in stream so that I can process my tree at once. If this is something that's just more suited to a script than a single line, that's fine too. Coming from the glorious world of BASH my brain just wanted to one-line it.
Get-QADUser -Enabled -Email * -SizeLimit 0 |
Where-Object {$_.Email.Split('#')[0] -eq $_.FirstName }
Try this:
Get-QADUser -enable -IncludedProperties PrimarySMTPAddress |
? { $_.PrimarySMTPAddress -match ("^"+[regex]::escape("$($_.firstname)")+"#domain.com") }

Resources