Rename AD groups using two PowerShell arrays - 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.

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.

How to output Get-ADComputer PowerShell results to JSON format containing IP and a parent object (Eg: Windows Servers)?

I did the following ps1 script:
Import-Module ActiveDirectory
Get-ADComputer -Filter "OperatingSystem -Like '*Windows Server*' -and Enabled -eq 'True'" | Select-Object Name, DNSHostName | ConvertTo-Json | Out-File "C:\adServers.json"
The output is:
[
{
"Name": "exampleServer1",
"DNSHostName": "exampleServer1.domain.com"
},
{
"Name": "exampleServer2",
"DNSHostName": "exampleServer2.domain.com"
} ]
It generates a list with all the objects that are described as "Windows Servers".
But what i want to achieve is:
I will do more of this, but instead of Windows Servers i will also include lists with Linux Servers, and some other devices.
I also need that one object contains the value IP Address. (Name, DNSHostName, IP Address)
Since i will gather multiple devices with different descriptions, i need a title for each list i generate. Below a example for easier understanding...
Here's what i want to achieve, the output would be on the following JSON format:
{
"Linux Servers": [{
"Name": "exampleServer3",
"DNSHostName": "exampleServer3.domain.com",
"IP": "192.168.1.3"
},
{
"Name": "exampleServer3",
"DNSHostName": "exampleServer3.domain.com",
"IP": "192.168.1.3"
}
],
"Windows Servers": [{
"Name": "exampleServer1",
"DNSHostName": "exampleServer1.domain.com",
"IP": "192.168.1.1"
},
{
"Name": "exampleServer2",
"DNSHostName": "exampleServer2.domain.com",
"IP": "192.168.1.2"
}
]
}
Does anyone knows how can i improve my code in order to do that?
I'm working on this because i will use it as a Discovery Rule for Zabbix Monitoring Software.
I don't really know on how or where to start.
Any tips or suggestion is really appreciate it...
PS: I will keep updating this answer with the codes i come up with, and all the tests i do.
Thanks in advance...
Edit:
I was able to solve this by using mklement0' and Mathias' excelent explanation for grouping the objects using Group-Object.
Final result:
Import-Module ActiveDirectory
$listaServidores = [ordered] #{}
# Raw command:
# Get-ADComputer -Filter "(OperatingSystem -Like '*' -and Enabled -eq 'True')" -Property OperatingSystem, IPv4Address |
# Select-Object Name, DNSHostName, IPv4Address, OperatingSystem |
# Exportar para JSON: ConvertTo-Json | Set-Content -Encoding Utf8 "C:\adServers.json"
Get-ADComputer -Filter "(OperatingSystem -Like '*Windows Server*' -and Enabled -eq 'True')" -Property OperatingSystem, IPv4Address |
Select-Object Name, DNSHostName, IPv4Address, OperatingSystem |
Group-Object {
switch -Wildcard ($_.OperatingSystem) {
'*Windows Server*' { 'Windows Servers' }
'*Windows 7*' { 'Outdated Computers' } # I will use this later. Outdated users, need to update to Windows 10
Default { 'Others' } # Other devices Discovery Rules. Will dig around later
}
} |
ForEach-Object {
$listaServidores[$_.Name] =
#($_.Group | Select-Object Name, DNSHostName, #{ Name='IP'; Expression='IPv4Address'})
}
$listaServidores | ConvertTo-Json -Depth 3 # !! -Depth is needed to avoid truncation
# Set-Content -Encoding Utf8 "C:\adServers.json"
You can use the Group-Object cmdlet to group computers by their .OperatingSystem property, which allows you to construct an ordered hashtable[1] that translates into the desired JSON structure when passed to ConvertTo-Json:
Import-Module ActiveDirectory
# Initialize an ordered hashtable that will collect the groups.
$orderedHash = [ordered] #{}
Get-ADComputer -Filter "Enabled -eq 'True'" -Property DNSHostName, IPv4Address, OperatingSystem |
Group-Object OperatingSystem | # See below re higher-level grouping.
ForEach-Object {
# Add an entry named for the current group (OS)
# with the array of the group's members as the value.
$orderedHash[$_.Name] =
#($_.Group | Select-Object Name, DNSHostName, #{ Name='IP'; Expression='IPv4Address'})
}
# Convert to JSON and save to a file.
$orderedHash |
ConvertTo-Json -Depth 3 | # !! -Depth is needed to avoid truncation
Set-Content -Encoding Utf8 C:\adServers.json
Mathias' helpful answer shows how to create groups explicitly, based on individually filtered Get-ADComputer calls, which allows arbitrary, higher-level groupings, as well as limiting processing to only computers of interest.
If processing all (enabled) computers is desired, you can achieve higher-level grouping with a tweak to the above solution too, by passing a script block ({ ... }) that performs the mapping as the (positionally implied) -Property argument.
E.g., instead of Group-Object OperatingSystem, you could to the following:
Group-Object {
switch -Wildcard ($_.OperatingSystem) {
'*Windows*' { 'Windows Servers' }
'*Linux*' { 'Linux Servers' }
Default { 'Others' }
}
}
[1]
An ordered hashtable ([ordered] #{ ... }) - unlike a regular hashtable (#{ ... }) - preserves the definition order of its entries.
[ordered] is the only qualifier supported, and while it looks like a cast to a (nonexistent) [ordered] type, it isn't; instead, it is syntactic sugar that that translates into a System.Collections.Specialized.OrderedDictionary instance (vs. System.Collections.Hashtable for a regular hashtable).
See the conceptual about_Hash_Tables help topic.
mklement0's excellent answer shows you how to automatically group the computers by the OperatingSystem property and construct the resulting JSON based on those.
But what if you want a different label for each group, or you want some of them grouped together?
If you know how you want to label them up front, you can organize each group into a hashtable and associate the query filter you want to use for each:
$OSGroups = #{
'LinuxServers' = "OperatingSystem -Like '*Windows Server*' -and Enabled -eq 'True'"
'WindowsServers' = "OperatingSystem -Like '*Windows Server*' -and Enabled -eq 'True'"
}
Now we just need to use each of those entries to fetch the relevant computers and add them to a property named for the key (eg. WindowsServers) on an output object:
# prepare another hashtable to hold all the information
$outputData = [ordered]#{}
# loop throw the different groups of computers you want to report on
foreach($kvp in $OSGroups.GetEnumerator())
{
$groupName = $kvp.Key
$groupFilter = $kvp.Value
# Assign the output for the given filter to the appropriate entry in our output dictionary
$outputData[$kvp.Key] = Get-ADComputer -Filter $kvp.Value -and Enabled -eq 'True'" | Select-Object Name, DNSHostName
}
# this will now produce the desired output format
$outputData |ConvertTo-Json -Depth 3 |Set-Content path\to\out.json

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

Recursively delete folders where folder name contains a number

Good afternoon all,
I am attempting to delete folders at a specific location containing a number in the name, which can be any number in the array.
$fso = New-Object -com "Scripting.FileSystemObject"
$Versionarray = (13..20)
$folder =
$fso.GetFolder("$env:USERPROFILE\appdata\local\Microsoft\OneDrive")
foreach ($subfolder in $folder.SubFolders)
{
If ($subfolder.Name -match "$Versionarray")
{
remove-item $subfolder.Path -Verbose
}
}
Please see an example of the following folders it will sift through below:
18.172.0826.0010
18.172.0826.0010_2
18.172.0826.0015
18.172.0920.0015
18.172.0920.0015_1
logs
settings
setup
If I change the "VersionArray"array to the variable "18" instead, it will start to remove the folders. It doesn't appear to be going through each number of the array. I need it to be an array to future-proof the script as the number represents a version of OneDrive.
Thank you for looking over this.
Going from your initial idea to have a list of items that must be contained in the subfolder name, we can do a pipeline like this:
$Versionarray = 13..20
Get-ChildItem "$env:LOCALAPPDATA\Microsoft\OneDrive" -Recurse | Where-Object {
$item = $_
$item -is [System.IO.DirectoryInfo] -and (
$Versionarray | Where-Object { $item.Name.Contains($_) }
)
} | Remove-Item -WhatIf
Notes:
Get-ChildItem returns all subfolders and files in a folder. Drop -Recurse if you don't need that.
Where-Object filters any list of objects according to a condition. Any result other than 0, $false, $null, or the empty string/empty list will be considered $true. It's not necessary to actually return $true, as long as anything is returned at all.
$_ is the "current item" in the pipeline
$foo -is [Fully.Qualified.ClassName] checks if an object is of a certain class. In this case, we only want to look at System.IO.DirectoryInfo objects and ignore all files.
$Versionarray | Where-Object { $item.Name.Contains($_) } filters the $Versionarray down to those elements that are contained in the folder name. You could use .StartsWith() or any other method of .NET strings in its place.
Any object that "survives" the Where-Object filter is passed to Remove-Item
-WhatIf performs a dry-run, drop it when you're sure the right thing will happen.

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