Simple PowerShell question - How to convert object into arraylist - arrays

I have a variable that contains the following:
Site IP
walmart 10.20.30.40
walmart 10.20.30.41
walmart 10.20.30.42
target 10.20.30.50
target 10.20.30.51
hm 10.20.30.60
This is an object created using New-Object psobject -Property #{ site = <code> ; IP = <code> }. So if I call $obj.site, it does list only the sites and same goes with IP.
I want to convert this is into an arraylist grouped by the sites.
For instance, result output should look like this:
walmart : {10.20.30.40, 10.20.30.41, 10.20.30.42}
target : {10.20.30.50, 10.20.30.51}
hm : {10.20.30.60}
I want to be able to call this from my script like for reasons like adding or subtracting IPs on any site. For instance:
$myArray.target
Which should list the IPs like:
10.20.30.50
10.20.30.51
Instead of the same format - {10.20.30.50, 10.20.30.51}
Then I would add more IPs to these object using the Add method.
($myArray.walmart).Add("10.20.30.44")
The closest I've been able to get is the following (and its output):
$obj | Group-Object -Property Site | Select-Object Name,Group
Name Group
walmart {#{Site=walmart; IP=10.20.30.40}, #{Site=walmart; IP=10.20.30.41}, #{Site=walmart; IP=10.20.30.42}
target {#{Site=target; IP=10.20.30.50}, #{Site=target; IP=10.20.30.51}
hm {#{Site=hm; IP=10.20.30.60}
With that output, I can't call on particular site like $myArray.walmart.
I'm pretty new to powershell, what am I missing? Any help would be much appreciated!

try Something like this:
$Array=#(
[pscustomobject]#{Site='walmart'; ID='10.20.30.40'}
[pscustomobject]#{Site='walmart'; ID='10.20.30.41'}
[pscustomobject]#{Site='walmart'; ID='10.20.30.42'}
[pscustomobject]#{Site='target'; ID='10.20.30.50'}
[pscustomobject]#{Site='target'; ID='10.20.30.51'}
[pscustomobject]#{Site='hm'; ID='10.20.30.60'}
)
$Object=New-Object PSObject
$Array | group Site | %{
#create an new list with all id for the curretn group
$CurrentList = New-Object System.Collections.Generic.List[System.Object]
$_.group.ID | %{$CurrentList.Add($_)}
#Add property and list to the final object
Add-Member Noteproperty -Name $_.Name -value $CurrentList -InputObject $Object
}
#add element to a site
$Object.hm.Add("NewID")

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.

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 $_"
}

Powershell: Assign properties to existing array

I have a single dimensional array that I get from either a get-content command or from multi-line text box input. I want to assign a property to the entries in this array, then add more properties to use later in my script.
Something like:
$items = new-object psobject
$items | add-member -membertype NoteProperty –name Name –value NotSet
$items | add-member -membertype NoteProperty –name Percent –value NotSet
$names = #($textboxInputText.Lines)
$names | % { $items | Add-Member noteproperty $_.Name $temp.($_.Name) }
foreach ($item in $items)
{
$percent = {script block}
$item.percent = $percent
}
I know this is broken code, but I wanted to give an example of where I was headed. I've searched far and wide but haven't been able to find exactly what I was looking for.
EDIT:
Code Goal: Get input from a text box or text file (single line entries). Have those entries be assigned to the "name" property, then add a second property to the array (Percent) that will need to be filled in with another block of code.
EDIT 2:
Collection is being used in the following code:
foreach ($item in $collection) {
$psConsoleFile = "PATH TO FILE.pc1"
$variable1 = "something"
$variable2 = "something else"
$command = ".`"Command1 $item.name | Command2 -Switch $variable1 -Switch2 $variable2`""
$OutputScriptBlock = "powershell.exe -PSConsoleFile $psConsoleFile -command $command"
}
The output of this is as follows:
powershell.exe -PSConsoleFile "PATH TO FILE.psc1" -command ."Command1 #{Name=name1; Percentage=}.name | Command2 -Switch1 something"
Why is the code outputting the full row instead of the name?
Also, I'm using PS 4.0 for all implementations of this script.
Ok, I see the problem here. So you have an array of Strings that you got either from a multi-line text box form object, or from a text document with the Get-Content command, but what you really want is an array of PSObjects.
A string object can not have additional properties added to it like you want (well, not conventionally, let's just not go there because you won't be happy with where things end up, trust me on this one). Instead let's take that array of strings, and for each string create a PSObject for it like you want. You will want a ForEach-Object loop for this to be simple. Either way you will want to pipe your input (either the textbox or the get-content command) to a ForEach loop, and you can assign the whole thing to a variable that will collect all of the objects to be worked with later (to update the Percent property). Something like this should accomplish what you want:
[Array]$Collection = $textboxInputText.Lines | ForEach{
New-Object PSObject -Property #{
'Name' = $_
'Percentage' = $null
}
}
I specified $Collection as the type [Array] so that if you wanted to index into it later there wouldn't be any issues should your input only be a single item. Then if you want to update the percentages you can do that by running $Collection through a ForEach loop (either inline or not)
$Collection | ForEach{ $_.Percentage = {Script Block} }
or
ForEach($Item in $Collection){
$Item.Percentage = {Script Block}
}
Now, things to note here... You are not going to be able to just assign $Collection back to your textbox. You could probably assign $Collection.Name, but that may require a newer version of PS since I don't know how backwards compatible that is. If you use a Get-Content command instead of referencing the textbox, simply change $textboxInputText.Lines | ForEach{ to Get-Content "C:\Path\To\File.txt" | ForEach{ and you should be all set.
Edit: Ok, the problem you have now isn't with the object but with how you're trying to expand a property of it within a double quotes. To access the name you would have to create a sub expression within the double quotes by wrapping $Item.Name within $(). So that line for you would look like:
$command = ".`"Command1 $($item.name) | Command2 -Switch $variable1 -Switch2 $variable2`""

Powershell: How to dynamically build array or objects?

I would like to collect some information about hosts in the domain, so I am trying to write something like this:
# declare array for storing final data
$servers_list = #()
#start with a list of servers and go through collecting the info
$servers | ForEach-Object {
$sys = Get-WmiObject Win32_computersystem -ComputerName $_
# create new custom object to store information
$server_obj = New-Object –TypeName PSObject
$server_obj | Add-Member –MemberType NoteProperty –Name Domain –Value $sys.Domain
# .... add all other relevant info in the same manner
# Add server object to the array
$servers_list += $server_obj
}
The problem with this code is that I pass a reference to the object into array and not the actual object. So by the time my loop is finished I end up with an array that contains rows that look all the same :(
Any idea how to pass actual object into array and not just a reference to it?
Another thought is to dynamically declare new object instead of using $server_obj variable every time but I am not sure how to do this either...
Thanks!!!
You can build an array of objects and keep dynamically adding information to them like this:
#This will be your array of objects
#In which we will keep adding objects from each computer
$Result = #()
#start with a list of servers and go through collecting the info
$servers | ForEach-Object {
$sys = Get-WmiObject Win32_computersystem -ComputerName $_
# create new custom object to keep adding store information to it
$Result += New-Object –TypeName PSObject -Property #{Domain = $sys.Domain;
Name = $sys.Name;
SystemType = $sys.SystemType
}
}
# Get back the objects
$Result
Where Domain,Name and SystemType are the properties that you want to associate with the objects.
It sounds like it is passing a reference, but I don't think it's the object that's being passed as a reference, but the property values. There are discrete objects, but they all have the same reference for their property values, so they all look the same. If that's the case,
$server_obj | Add-Member –MemberType NoteProperty –Name Domain –Value "$($sys.Domain)"
should make the value a string, which is a value type and won't change.
You're making this a little harder than it should be. Pass the server names from a query, csv or list then iterate over them. Select what you want from the result.
$info = "server1", "server2" | ForEach-Object{Get-WmiObject -Class win32_computersystem -ComputerName $_ } | Select-Object Domain, Name, Systemtype
$info[1].Domain will output domain.com

Resources