Test-Connection $False will not convert to ArrayList - arrays

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 }

Related

Trying to output a custom powershell object where I can align each line of two different variables containing Category:Description

I'm trying to do an network access control audit by grabbing a user's AD groups, their descriptions and then output them in a way shown by this example:
[User]
#[1]Groups : #[1]GroupDescription
#[2]...
#[3]...
Below is what I have at the moment.
$UserGroups = #{
User = Read-Host -Prompt "What user do You want to look up Access for?"
Groups = (Get-ADUser $User -Properties MemberOf).MemberOf
GroupsDescriptions = (Get-ADUser $User -Properties MemberOf).MemberOf | % {(Get-ADGroup $_ -Properties *).description}
}
$Object = New-Object psobject -Property $UserGroups
$Object | format-table | Export-Csv c:\tmp\test.csv
Though the output is very strange. I don't understand it. Below is a result of Get-Content C:tmp\test.csv
#TYPE Microsoft.PowerShell.Commands.Internal.Format.FormatStartData
"ClassId2e4f51ef21dd47e99d3c952918aff9cd","pageHeaderEntry","pageFooterEntry","autosizeInfo","shapeInfo","groupingEntry"
"033ecb2bc07a4d43b5ef94ed5a35d280",,,,"Microsoft.PowerShell.Commands.Internal.Format.TableHeaderInfo",
"9e210fe47d09416682b841769c78b8a3",,,,,
"27c87ef9bbda4f709f6b4002fa4af63c",,,,,
"4ec4f0187cb04f4cb6973460dfe252df",,,,,
"cf522b78d86c486691226b40aa69e95c",,,,,
I have tried outputting to a .txt file using Out-file, but I always get each property cut off with a ... at the end. I've used the -Autosize and -Expand when formatting the data before piping it to the export line.
Any Suggestions or advice would be extremely helpful.
Things I'll be Looking at later
Go through each line in PowerShell object and extract variables
Powershell & ActiveDirectory - trying to output users in a group and their membership
Out-file crops my text when trying to output a table
Thanks!
As stated, only ever use Format-* cmdlets to produce for-display output, never for outputting data that must be processed programmatically later. What Format-Table outputs are objects representing formatting instructions, and it is their properties that ended up in your CSV file - see this answer for more information.
In order to include collections (arrays) in CSV output, you must convert them to a single string, using a self-chosen separator. Otherwise, Export-Csv simply calls .ToString() on the collection object itself, which yields the collection's type name, and no information about its elements.
Therefore, use something like the following, which uses ', ' as the separator string to represent the group names and descriptions in a single column each:
$UserGroups = [pscustomobject] #{
User = ($user = Read-Host -Prompt "What user do You want to look up Access for?")
Groups = ($groups = (Get-ADUser $User -Properties MemberOf).MemberOf) -join ', '
GroupsDescriptions = (
$groups | ForEach-Object { (Get-ADGroup $_ -Properties *).Description }
) -join ', '
}
$UserGroups | Export-Csv c:\tmp\test.csv
Note:
[pscustomobject] #{ ... } is used to directly construct a custom object, which is syntactic sugar available since PowerShell v3 that is simpler and more efficient than a New-Object call.
In order to use the result from your Read-Host call in later properties of your object definition, you must cache it in aux. variable $user (note that enclosing the assignment in (...) passes its value through.
Similarly, the result of the Get-ADUser call is cached in aux. variable $groups, so that it doesn't have to be repeated in the GroupsDescriptions value.
However, as zett42 points out, it may be cleaner to make the $user = ... and $groups = ... assignments separate statements and place them before the object construction.
The problem is that you pipe to Format-Table before you pipe to Export-Csv. Only use Format-Table for displaying things on screen. The fix is to just remove that.
$Object | Export-Csv c:\tmp\test.csv
Thanks to this post Here and mklement0. I was able to figure out the formatting portion of this problem.
Now I have the remaining code that exports it exactly as intended.
$user= Read-Host -Prompt "What user bonehead?"
$object = Get-ADPrincipalGroupMembership $user
$Table = $object | ForEach-Object {
[pscustomobject] #{
Groups = $_.Name
GroupDesc = (Get-ADGroup $_ -Properties *).Description
GroupOwner = (Get-ADGroup $_ -Properties *).Info
}
}
$Table | Export-csv -NoTypeInformation c:\tmp\test.csv
The -NoTypeInformation helps eliminate the header on the .csv files and the piped Group info through the ForEach-Object cmdlet helped insure every object had it's own row in excel.

Check a object-string against a array of exceptions

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

Rename AD groups using two PowerShell arrays

I'm trying to rename existing AD groups in this way.
AD groups starting # to be renamed to the same name without #. For example , I have #dl1 and I wish to get it renamed dl1 (omitting #)
Im trying to rename following four users first.
I have written two arrays, in this manner. ($myArray and $myArray2).
$myArray =#(
$data = Get-ADGroup -Filter {name -like "#*"} |select samaccountname
$data.samaccountname | foreach {$_.split("#")[1]
}
)
$myArray2 =#(
$assdf=Get-ADGroup -Filter {name -like "#*"}
$myArray2 =#($assdf)
$num=0
foreach($a in $assdf)
{
$myArray2[$num]
$num=$num+1
}
)
If I print $myarray it gives exact results, that I wish, in this way.
and also if I print $myarray2 it gives the desired results in this way,
the missing piece of the puzzle is combining those two arrays to run the final command that is
set-adgroup -identity (members indide $myArray2) -samaccountname (members indide $myArray)
For hours, I have tried numerous methods to get set-adgroup .. using for each loop etc.
for example,
$a=0
foreach ($item in $myArray2)
{
$nameto_replace=$myArray[$a]
Set-adgroup -identity $item.samaccountname -samaccountname $nameto_replace
$a=$a+1
}
Can anyone please shed some light, please? I am totally out of ideas now. thanks in advance
There is no need to perform Get-ADGroup twice, where you can use it once and loop over the results in a ForEach-Object loop:
Updated as per Aravinda's helpful observation
Get-ADGroup -Filter "Name -like '#*'" | ForEach-Object {
$newName = $_.Name.TrimStart('#')
Write-Host "Renaming group $($_.Name).. to '$newName'"
# replace only the SamAccountName
$_ | Set-ADGroup -SamAccountName $newName
# or replace multiple properties at the same time.
# You need to use the LDAP names here, so mind the casing !
# See http://www.selfadsi.org/group-attributes.htm
# $_ | Set-ADGroup -Replace #{sAMAccountName = $newName; displayName = $newName}
}
You can limit the search to a specified OU if you want by adding the OU's DistinguishedName with the -SearchBase parameter
Theo's answer is fantastic!
Following is the one finally I used derived from theo's answer.
Get-ADGroup -Filter "Name -like '#*'" | ForEach-Object {
$newName = $_.Name.TrimStart('#')
$_ | Set-ADGroup -Replace #{sAMAccountName = $newName;displayName = $newName}
$_ | Rename-ADObject -NewName $newName
}
If you try using set-adgroup to change 'name' and 'CN' and it gives below error.
"Set-ADGroup : The directory service cannot perform the requested operation on the RDN attribute of an object"
To change multiple attributes, especially including Name and CN, combination of Rename-ADObject and Set-ADGroup can be used.

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

ACL "fuzzy" comparision

I'm trying to compare ACLs on a folder with a reference set of ACLs, and then list any exceptions. The "fuzzy" part of the equation is that I want to be able to disregard any unknown SID. So creating a reference folder with the perms I want to test won't work to use Compare-Object between it and my test folder.
The underlying scenario is that I am cleaning up old user directories where the actual user account has been deleted (this is where the non-resolved SID comes in). By default, the folders include perms for Administrator and the like, which I don't care about. There are some folders, however, where another user has been granted explicit permissions, and I want to capture these. Unfortunately, there aren't any shortcuts I can use to check: e.g. -IsInherited or the like to exclude ACLs I don't care about.
Per the below, I can dump the ACLs out into an array
$acl = get-acl f:\user_folder
$access = $acl.Access | ForEach-Object { $_.identityReference.value }
$access
BUILTIN\Administrators
MYDOMAIN\JBLOGGS
S-1-5-21-4444444444-9999999-1111111111-74390
MYDOMAIN\Domain_Group ###Yes, the group has an underscore in the name
I can create another array of the users I want to ignore, including a partial string to match any unresolved SID.
$defaults = #("BUILTIN\Administrators","MYDOMAIN\DomainGroup","S-1-5-21")
So how do I compare my $defaults array with the $access array and output only the exceptions like "MYDOMAIN\JBLOGGS"?
I'm trying a foreach, but I'm stumped about grabbing that exception. The following still outputs the SID I want to avoid. I'm hoping to also avoid too many nested "IFs".
$access | ForEach { If ($defaults -notcontains $_) { Write-Output $_ } }
MYDOMAIN\JBLOGGS
S-1-5-21-4444444444-9999999-1111111111-74390 #Do not want!
If I put the wildcard $_* into the -notcontains, I get the whole contents of $access again.
I'd do something like this:
$defaults = 'BUILTIN\Administrators', 'MYDOMAIN\DomainGroup', 'S-1-5-21*'
$acl.Access | Where-Object {
$id = $_.IdentityReference
-not ($defaults | Where-Object { $_ -like $id })
} | Select-Object -Expand value
$defaults | Where-Object { $_ -like $id } does a wildcard match of the given identity against all items of $defaults. The wildcard * at the end of S-1-5-21* allows to match all strings starting with S-1-5-21. The negation -not inverts the result so that only identities not having a match in $defaults pass the filter.
give the users you want to ignore some right on a dummy folder, get the acl of that folder and then compare whith the acl of your actual folder
$genericACL = get-acl c:\temp\dummy
$folderacl = get-acl f:\user_folder
$exceptions= $folderacl.Access.identityreference.value |?{ ($_ -notin $genericACL.access.identityreference.value) -and ($_.strartswith('S-1-5-21') -eq $false)) }
In the end, it was fairly simple, thanks to the help above.
I managed to omit the fact in the original question where I required it to work in Powershell v2.
$defaults = #("BUILTIN\Administrators","MYDOMAIN\DomainGroup")
$acl = get-acl $folder
$access = $acl.Access | ForEach-Object { $_.identityReference.value }
# check that no other account still has access to the folder
$access | ForEach {
If ($defaultACL -notcontains $_ -and $_ -notlike 'S-1-5-21*') {
write-output "Extra perms:$user $_"
}

Resources