Tag Archives: Powershell

Find where a certificate is installed

Some organizations use wildcard certificates. The problem with these certificates is that they start to lead a life on it’s own. So when the time comes that such a certificate is about to expire one can wonder where is this certificate installed? Luckily we have Powershell to save us from having to open all certificate stores on all servers in the domain. At first I was  thinking maybe the easiest way could be to have a Powershell remoting session. But that would require all servers to have Powershell remoting enabled. Unfortunately not all servers have it enabled or have a reasonable Powershell version to start with. So just for documenting purpose I’ll write down the command to find a certificate.

A certificate has a thumbprint. This thumbprint is equal on all servers as it part of the certificate, much like the serial number.  So you can easily find the thumbprint where your looking for running the command on a server where the certificate is present.

Just copy the thumbprint and enter it into the command to find the certificate like the on below

Invoke-Command {gci cert:\Localmachine\My | ? {$_.Thumbprint -eq "FF9C130AE6C1D18FAC43AF0416E44B7011307545"}} -ComputerName #COMPUTERNAME#

Replace #COMPUTERNAME#  for the computername you would want to question. Now this is all good but like I mentioned before I would want to find certificate regardless of Operation system contstrains or whether Powershell Remoting is enabled or not. So this script would process that accordingly. Change the thumbprint to the thumbprint of your certificate. The script export all servers where the certificate with the given thumbprint is present. Please feel free to modify this to your needs.

Function Get-Cert( $computer=$env:computername ){
    #Sets ReadOnly flag upon external Certificate store.
	$ro=[System.Security.Cryptography.X509Certificates.OpenFlags]"ReadOnly"
	#Opens LocalMachine store. You could potentially enter a different Cert store. 
	$lm=[System.Security.Cryptography.X509Certificates.StoreLocation]"LocalMachine"
	#Opens personal certificate store from local machine. 
	$store=new-object System.Security.Cryptography.X509Certificates.X509Store("\\$computer\My",$lm)
	#Establish connection with remote PC
    $store.Open($ro)

	#Display certificates in console or variable
	$store.Certificates

}

#Opens list
$Thumbprint = "FF9C130AE6C1D18FAC43AF0416E44B7011307545" 
$List = @()

#Servers reset their machine password within a specific timeframe
$Servers = Get-ADComputer -Filter * -Properties OperatingSystem,PasswordLastSet | ?{$_.OperatingSystem -match "Server" -and $_.PasswordLastSet -gt (Get-Date).AddDays(-60)}

Foreach ($Server in $Servers)
	{
	$Server.DNSHostName 
	If ((Get-Cert $Server.DNSHostname | ? {$_.Thumbprint -eq $Thumbprint}).Count -ne 0)
		{
		$List += $Server.DNSHostName
		}
	}

$List | % {AC -Value $_ -Path ".\ComputersWithCertificate.txt"}

Count homedirectories

For management purposes creating a report with the directory size of users’s home directory you can use this script. It will count the total size of the home directory per user. It will get the user’s name based on the name of the home directory. This wil be written down in the file.

$list = @(GCI "\\Fileserver\users$" -Force| ?{$_.PsIsContainer -eq $True})
$Header1 = ("DisplayName;Gebruikersnaam;Grootte in MB")
$Header2 = ("Hoofdmap;Grootte in MB")
$Path1 = ".\Overzicht-H-Schijven.csv"
AC -Value $Header1 -Path $Path1
$Path1 = GI $Path1


Foreach ($Item in $List)
{
$Result = @(GCI ($Item.FullName) -Recurse -Force)
[Int]$Count = 0
Foreach ($ResultItem in $Result){$Count = $Count + $ResultItem.Length / 1MB}
$Value = ((Get-ADUser $Item.Name).Name + ";" + $Item.Name+ ";" + $Count)
AC -Value $Value -Path $Path1
}

$Body = "Beste,

Hierbij het overzicht van de grootte van de H Schijven van de gebruikers van uw organisatie.

Groet,
"
$list = Import-CSV $Path1.FullName -Delimiter ";"
$list | % { $_."Grootte in MB" = [int]$_."Grootte in MB" }
$list = $list | Sort -Property Gebruikersnaam -Unique | Sort -Property "Grootte in MB" -Descending
$list | Export-CSV $Path1.FullName -Delimiter ";" -NoTypeInformation -Force

Send-MailMessage -From "servicedesk@company.nl" -Subject "Gebruiker overzicht " -To "emailadress@domain.tld" -Body $Body -SmtpServer "mailserver" -Attachments $Path1.FullName

RI $Path1 -Force

Connecting to Powershell Endpoints Exchange/Lync

I will write a short post about connecting to Powershell endpoints of Exchange and Lync. Basically you can always use PSRemoting to connect and make use of modules. However some CMDlets will simply not work when importing the snapin or module. So connecting to an endpoint is much more efficient. To connect to Microsoft Exchange use the following commands:

$PSOptions = New-PSSessionOption -SkipCACheck -SkipRevocationCheck -SkipCNCheck
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ExchangeServer.Domain.TLD/powershell -SessionOption $PSOptions
Import-PSSession $Session 

To connect to Lync use the following commands:

$Session = New-PSSession  -ConnectionUri https://LyncServer.Domain.TLD/ocspowershell -Authentication NegotiateWithImplicitCredential
Import-PSSession $Session

You can connect from any domain computer. Of course if your loged on with an account that does not have to appropriate permission you’ll have to connect using the  -credentials parameter.

 

ShareFile edit Storage quota limit

I just wrote an opinion about the Powershell module of Citrix ShareFile. Now let’s get to it. This is the fun part. You’ll have to take a close look at how Citrix describes things. So if you want to manage the StorageQuotaLimitGB field using Powershell, you can use the script below. I’ll get in detail about it.

Add-PSSnapin ShareFile
$SFClient = Get-SfClient -Name File.sfps
$User = Send-SfRequest $SFClient -Method GET -Entity "Accounts\Employees" | ?{$_.Email -match "EmailAddress@Domain.TLD"}
$Property = new-object ShareFile.Api.Models.AccountUser
$NewQuota = ([int]$Quota = (Send-SfRequest $SFClient -Method GET -Entity "users" -Id $User.id).StorageQuotaLimitGB) + "5"
$Property.StorageQuotaLimitGB = $NewQuota
$Uri = $user.url.OriginalString.replace("Contacts","Users/AccountUser")
$Result = Send-SfRequest -Client $SFClient -Method Patch -Uri $Uri  -Body $property
If ($Result.StorageQuotaLimitGB -notmatch $NewQuota){Write-Error "Value is niet aangepast"}

This script will export all users and then select the user with the e-mailaddress specified. You can of course enter a string there with a specific value. Then it creates a new property with the value AccountUser. See it like this you want to edit this value but it is not in the Entity Users but one endpoint lower. It is in the entity AccountUser. AccountUser is below the entity User. If you for example would enter -Entity “Users”, the script will fail because the value StorageQuotaLimitGB is simply not present at that level. At first I got this wrong.  So that’s why you set this value lower. It is described on the site of Citrix but just a bit cryptic. Look for this part of the text:

StorageQuotaLimitGB

So then the script get the current limit and adds an additional 5 GB to that limit. This is caught in a integer. Than I get the URL. Please not that here in the EU we have a different link. This script is not impacted by that but this explanation is. ShareFile uses this link:

https://ShareFileDomainName.sf-api.eu/sf/v3/Users(ID)

Well if you use the Entity Users the link above will be used. As said above you’ll want to point to the endpoint AccountUser. So the script changes the current uri from contacts to this:

https://ShareFileDomainName.sf-api.eu/sf/v3/Users/AccountUser(ID)

That is the magic. Next the script sends a request to the REST API of ShareFile to apply the new StorageQuotaLimitGB. At last it checks if the change is actually applied. If it isn’t a error is written.

Powershell ShareFile

When a software developer introduces a Powershell module or snapin I’m always excited about it. So Citrix released a Powershell snapin for ShareFile. There are some good thing about it, some bad things and one ugly thing.

The bad

So Citrix says that the ShareFile module is without the guarantee that it’ll actually work. See it like a beta product. This directly indicates that you’ll find very few documentation about. But even more worrying is that you’re on your own because it is unsupported. Citrix released a Powershell version but at the same time there’s a C# and a Java module. They’ve build this on a REST API. This is beautiful because in a way you’re not really bound to the ShareFile modules. As long as you can talk to the REST API. The API is on version 3 so I think it’s pretty stable and should lose it “in development” state.  But the lack of good documentation and

The Ugly

Well first and foremost, an administrator can create a SFPS file. This file can be used to authenticate to the ShareFile REST API. So you can create a carefree file that could be used by a first in line administrator or a script. And so you don’t have to share the credentials.  By default there’s no expiration date. Meaning that if this file falls in the wrong hands, one can truly demolish your entire environment. A lazy administrator could potentially compromise his entire environment. Via Powershell you can actually get the files in the repository of a ShareFile User. This is a extreme risk of which many administrators are unaware.

The Good

Well there’s a lot good about this. In most cases if there’s a REST API, the GUI is build upon that. So anything you can do in the GUI, can be done using the API. This is an incredible feature set. You can completely manage your environment using the API. I created some scripts to manage the ShareFile API. Right now the community is rather small, but as soon as this grows I’m sure there’s going to be a large knowledge base where you can find help.

If you get the authentication right and the scripts ready I’m sure that this will ease the pressure of the back of the administrators. I’m quite positive anyway.

Correct data in mobile field in AD

A script changed the format of values in the Mobile field in AD. This however caused errors in the operation of soft tokens. I made an export of all mobile phone numbers and noticed that there were more errors. As so I’ve made a script that uses the substring method to correct the values. Please feel free to change it as desired.

$List2 = @()
$List = Get-ADUser -Filter * -Properties Mobile | ?{$_.Mobile -ne $Null}
Foreach ($Item in $List)
	{
	If ($Item.Mobile.Substring(0,2) -notmatch "06" -or $Item.Mobile.Length -ne "10")
		{
		$List2 += $Item
		}
	}

Foreach ($Item in $List2)
	{
	If ($Item.Mobile.Substring(0,3) -match "316"){Set-ADUser -Identity $Item.sAMAccountName -MobilePhone ($item.mobile.Substring(0,3).Replace("316","06") + $Item.Mobile.Substring(3))}
	If ($Item.Mobile.Substring(0,1) -match "6"){Set-ADUser -Identity $Item.sAMAccountName -MobilePhone ($item.mobile.Substring(0,1).Replace("6","06") + $Item.Mobile.Substring(1))}
	If ($Item.Mobile.Substring(0,4) -match "\+316"){Set-ADUser -Identity $Item.sAMAccountName -MobilePhone ($item.mobile.Substring(0,4).Replace('+316',"06") + $Item.Mobile.Substring(4))}
	If ($Item.Mobile.Substring(0,5) -match "00316"){Set-ADUser -Identity $Item.sAMAccountName -MobilePhone ($item.mobile.Substring(0,5).Replace("00316","06") + $Item.Mobile.Substring(5))}	
	If ($Item.Mobile.Substring(0,3) -match "013"){Set-ADUser -Identity $Item.sAMAccountName -MobilePhone $Null}	
	If ($Item.Mobile -match " "){Set-ADUser -Identity $Item.sAMAccountName -MobilePhone ($Item.Mobile.Replace(" ",""))}
	If ($Item.Mobile -match "-"){Set-ADUser -Identity $Item.sAMAccountName -MobilePhone ($Item.Mobile.Replace("-",""))}
	If ($Item.Mobile -match "Geen"){Set-ADUser -Identity $Item.sAMAccountName -MobilePhone $Null}
	}

Connecting to servers outside a domain using Powershell

From time to time you’ll find yourself running scripts to a server outside the domain. If you have the same credentials on that as on the domain there’s no problem. However in most cases you don’t. Normally you will have to encrypt the password as a secure string. However there are cases that such a solution costs to much time. Especially if you have multiple passwords for the same user account (eg. Administrator). So to get this done you might use a old trick to connect to the server using SMB en store the credentials session in the memory of the server running the script. I must admit that this is only if you’ll have to go quick and dirty. If’ve used it once and it may come in handy sometimes.

$Credentiallist = @()
$Credentiallist += "Password1"
$Credentiallist += "Password2"
$Credentiallist += "Password3"
$Credentiallist += "Password4"
$Errors = ".\Error.csv"
$Success = ".\Success.csv"
$net = new-object -ComObject WScript.Network
$ADComputers = Import-CSV ".\DNSNames.csv"
$Computers =@()
$ComputerSuccess =@()
$FailedComputers = @()
Foreach ($ADComputer in $AdComputers)
	{
	$ADComputer = $ADComputer.DNSHostName
	Foreach ($cred in $credentialList)
		{
		If ($ComputerSuccess -notcontains $ADComputer)
			{ 
			$net.MapNetworkDrive("u:", "\\$ADComputer\c$", $false, "localhost\Administrator", "$Cred")
			If((Test-Path "\\$ADComputer\C$\Windows" ) -eq $True)
				{
				$ComputerSuccess += $ADComputer
				#Do the code you want to run against the server
				}
			}
		}
	If ((get-psdrive -name "U" -Erroraction SilentlyContinue) -ne $null){$net.RemoveNetworkDrive("u:")}
	}

Find user session on all active servers

If you want to find a session on all active servers in the domain you can make use of the command quser /server:whatever. This displays the session details from that server. This is handy if some administrator has locked his account because of old sessions on some server. There’re alternative ways to find this. I made a post awhile back. See it here.

IPMO ActiveDirectory

$Username = "SAMACCOUNTNAME"
$List2 = @()

$List = Get-ADComputer -Properties passwordlastset,operatingsystem -Filter * | ?{$_.PasswordLastSet -gt (Get-Date).Adddays(-30) -and $_.OperatingSystem -match "Server"}

Foreach ($item in $list){
	[String]$Server = $item.Name
	$Result =@( (quser /server:$Server) -replace '\s{2,}',','|ConvertFrom-Csv)
		Foreach ($object in $result){
			If ($Object.UserName -Match $Username){$Output = ($Server + ";" + $Object.UserName + ";" + $Object.State)
			$List2 += $Output
		}
	}
} 

vCenter VM reboot script

Once in a while I run into problems that are really fun resolving. A colleague  of mine said that he was early today to fix the XenApp servers that did not reboot successfully. He said that since the storage of the Citrix Provisioning server was moved from flash to SAS the XenApp servers sometimes cannot boot. A shortage in IOPS is supposed to be the cause of this trouble. However all Provisioning servers combined consume over a TeraByte of storage. During the day this fast storage does not is nearly idle. Only during boot of the XenApp machines it is used in an appropriate manner. So that is quite costly.

VMWare created CMDlet’s the right way. You can basically do anything you can in GUI via Powershell using the PowerCLI.  The PowerCLI is an incredible tool in automatising stuff. We have RES Automation Manager. So I put the bits together and created A Powershell script in RES Automation Manager that runs every night. This script uses a few parameters which you can see in the code in the top section of the script. The script heavily relies on PowerCLI 6.0 This version works for any vCenter server.

The script connects to a VIserver (in this case the vCenter server). Next it gathers a list of all servers. Then it filters server based on the condition if it is PoweredOn and it matches on Name. Next it gets the VMware Tools status. If the status is NotRunning it will be applicable for a restart. The servers will be rebooted in a staggered way. So every two minutes a server will reboot. There’ll be a second attempt if the first attempt fails. At last it sends an e-mail with all server that fit the condition the first time, the second time and server that did not rebooted well.

I hope you can use it.

# Adds the base cmdlets
Add-PSSnapin VMware.VimAutomation.Core
$Recipients = $[Recipients]
$VIServer = "$[VIServer]"
$NotCondition = "$[NotCondition]"
$Condition = "$[Condition]"
$Username = [Environment]::UserName
$Hostname = $env:COMPUTERNAME
$Mailserver = "$[MailServer]"
$Level = "0"
Connect-VIServer $VIServer

#List bouwen van VM's die voldoen aan de voorwaarden
$List =@(Get-VM | ? {$_.Name -match $Condition -and $_.PowerState -match "PoweredOn" -and $_.Name -notmatch $NotCondition} | Get-VMGuest | ? {$_.State -match "NotRunning"})

#Check of de PowerCLi wel geinstalleerd is
If (((Get-PSSnapin).name -match "VMware.VimAutomation") -eq $Null){$Body = "De computer $Hostname heeft geen PowerCLI geinstalleerd staan. Installeer PowerCLI en probeer het opnieuw "; $Level = "1"}

#Check of er een verbinding is met de VI server
If ($global:DefaultVIServer -eq $null -and $Level -ne "1"){$Body = "Kon geen verbinding krijgen met de VCenter Server: $VIServer. Verander de waarde in het script als de servernaam veranderd is. Controleer of de user $UserName rechten heeft op de VCenter server. Controleer eventueel of de PowerCLI tools op de server wel compatible zijn met de Vcenter Server versie." ; $Level = "2"}

#Geen servers gevonden
If ($list.count -eq "0" -and $Level -eq "0"){$Body = "Er zijn geen server(s) gevonden welke voldoen aan de voorwaarden."}

#Actie bij wel servers gevonden
If ($list.count -gt "0" -and $Level -eq "0"){
$Body = "Er waren "+ $list.count +" server(s) die voldeden aan de voorwaarden. Het gaat om de server(s):`n`n"
$Body = "Er worden "+ $list.count +"servers herstart. De servers zijn:`n`n"
Foreach ($item in $list){$Body = $Body + $Item.vm + "`r"}

#Herstarten van servers
Foreach ($item in $list){Restart-VM -VM $Item.VM -Confirm:$False ; Start-Sleep -s 120}

#Check of er nu geen servers meer zijn die voldoen aan de voorwaarden
$List2 =@(Get-VM | ? {$_.Name -match $Condition -and $_.PowerState -match "PoweredOn" -and $_.Name -notmatch $NotCondition} | Get-VMGuest | ? {$_.State -match "NotRunning"})

#Actie bij wel servers gevonden die nog niet goed herstart zijn
If ($List2.Count -ne "0"){
$Body = $Body + "`n`n Er waren server die nog steeds niet goed herstart waren. De naam van deze server(s) waren:`n`n"
Foreach ($item2 in $list2){$Body = $Body + $Item2.vm + "`r"}
$Body = $Body + "`n`n Er worden geprobeerd de server opnieuw te herstarten."
Foreach ($item2 in $list2){Restart-VM -VM $Item2.VM -Confirm:$False ; Start-Sleep -s 120}
#Laatste check
$List3 =@(Get-VM | ? {$_.Name -match $Condition -and $_.PowerState -match "PoweredOn" -and $_.Name -notmatch $NotCondition} | Get-VMGuest | ? {$_.State -match "NotRunning"})
If ($List3.Count -ne "0"){
$Body = "Er zijn fouten gevonden bij het herstarten van servers. Zie onderaan de e-mail!!" + $Body
$Body = $Body + "`n`n De server die niet herstart zijn na twee pogingen zijn:`n`n"
Foreach ($item3 in $list3){$Body = $Body + $Item3.vm + "`r"}
}}}

#Mail versturen
Send-MailMessage -From "Rebootscript@domain.tld" -Subject "VMs rebooten" -To $Recipients -Body $Body -SmtpServer $MailServer

#Output naar Console in het geval dat er geen mail verstuurd is.
Write-host `n`n Mail Body`n
$Body

Filtering and Arraying 101 Powershell

So I try to teach my colleagues a bit more about Powershell from time to time. I have advised them a bit about how they can filter values to meet their requirements. Our architect wanted an export of all user accounts. When I simply exported all AD users in the domain he said that the amount of users is way too large. He asked me how this happend and where all those users are coming from? I wanted to give him an export of all enabled users in ActiveDirectory per OU. Actually I just wanted to show him the amount of users per OU. So I made a little script that does that job. However this is an excellent opportunity for someone to start working with Powershell. So I’ve written a little 101 about how they should start.

Step 1

Import the module ActiveDirectory.

IPMO ActiveDirectory

Step 2

You want to know in what OU a user is situated. Maybe the CMDlet Get-ADUser returns this value. By running the command Get-ADUser SAMACCOUNTNAME -Properties * you’ll get all the available properties. To display the methods and functions you can also use this command, in correspondence with the CMDlet Get-Member (Alias GM). You’ll have to run:

Get-ADUser SAMACCOUNTNAME -Properties * | GM

This prints all available methods,functions,properties and so forth. If you see a property, that is name something like, parent or OU you’ll be lucky. Unfortunately in Powershell 1,2 and 3 there isn’t such a property. So you have to find other ways to get the parent OU.

Step 3

Many CMDlets can be used against the registry, file systems, certificate stores or the Active Directory. In this case you’ll still have to know the containing OU. So probably the best way is to use the CanonicalName which contains this information. However that would kill all the fun. In this example DistinguishedName will have to do it. This obviously displays the containing OU. To save the DistinguishedName in a string run:

$Result = (Get-ADUser SAMACCOUNTNAME).DistinguishedName

Step 4

The DistinguishedName contains the CN of a user. You want to filter that out of the string. But how do you do that? Again use GM to see if you can convert the value to it’s parent or the get what is possible with the value.  To do so run the command:

$Result | GM

You can replace the CN as text. This can be really really tricky. To do so run the command in step 3 without the .DistinguishedName property behind it. Next run the command:

$Result.DistinguishedName.Replace(("CN="+$Result.Name+","),'')

There is no right or wrong if the result is the same and it meets your goals. That is an important lesson to learn.

Step 5

To select an item use the CMDlet Get-Item (Alias GI). This CMDlet will let you select any file, folder or object in the registry,file system, certificate store or AD. Actually you can use it against any mounted PSdrive. We still want to know the containing OU. So in this case we will look up the DistinguishedName in the AD. Again use GM to find out what is possible with the output. To do so run:

GI "AD:\$Result" | GM

Step 6

As you can see the property PSParentPath exists. Maybe this displays the required output? Run the command:

(GI AD:\$Result).PSParentPath

Step 7

That output is not how you want it to be. The type is displayed. To be as clean as possible you will have to replace that part of the line. You can use the Replace method like this:

(GI AD:\$Result).PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')

Step 8

To come back to the objective, we’ll have to export all enabled users. There are two ways to accomplish this.

$List = Get-ADUser -Filter * | ? {$_.Enabled -eq $True}

Or

$List = Get-ADUser -Filter {Enabled -eq $True}

Which one is better? The correct answer would be none. The result is what counts. The best thing is running the command that you can remember or that works best for you. In terms of performance the second command would be better. Try this:

(Measure-Command -Expression {$list = Get-ADUser -Filter * | ?{$_.Enabled -eq $True}}).TotalSeconds

(Measure-Command -Expression {$list = Get-ADUser -Filter {Enabled -eq $true}}).TotalSeconds

Step 9

Next you can form a “script” to collect all DistinguishedNames and replace the unwanted text. To do so run:

Foreach ($item in $List){$Result = (GI AD:\$item).PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')
AC -Value $Result -Path ".\Output.csv"}

Step 10

We have a list called Output.csv. The only thing we have to do now is to count the number of occurrences. This is not necessary the best way to do it. You can save this output in an array if you like.  Below there are examples of many different ways to solve this problem. Below is an example of the last bit of a script.

$List2 = GC ".\Output.csv"
$Uniquelist = $list2 | Sort -Unique
Foreach ($UniqueItem in $Uniquelist){
[int]$Count = 0
Foreach ($Item in $list2){If ($Item -eq $UniqueItem){$Count++}}
$Outinfo = "$UniqueItem" + ";" + "$Count"
AC -Value $outinfo -Path ".\Counted OUs.csv"
}

There are lots of ways to get to your destination. I have generated a number of scripts to do this task.

Script 1

IPMO ActiveDirectory
$list = Get-ADUser -Filter * | ? {$_.Enabled -eq $True}
Foreach ($item in $List){$Result = (GI AD:\$item).psparentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')
AC -Value $Result -Path ".\Output.csv"}
$List2 = GC ".\Output.csv"
$Uniquelist = $list2 | Sort -Unique
Foreach ($UniqueItem in $Uniquelist){
[int]$Count = 0
Foreach ($Item in $list2){If ($Item -eq $UniqueItem){$Count++}}
$Outinfo = "$UniqueItem" + ";" + "$Count"
AC -Value $outinfo -Path ".\Counted OUs.csv"
}

This script generated two output files. A part of statics values is replaced.

Script 2

IPMO ActiveDirectory
$list = Get-ADUser -Filter * | ? {$_.Enabled -eq $True}
Foreach ($item in $list){$Result = $item.distinguishedName.Replace(("CN="+$item.Name+","),'')
AC -Value $Result -Path ".\Output.csv"}
$List2 = GC ".\Output.csv"
$Uniquelist = $list2 | Sort -Unique
Foreach ($UniqueItem in $Uniquelist){
[int]$Count = 0
Foreach ($Item in $list2){If ($Item -eq $UniqueItem){$Count++}}
$Outinfo = "$UniqueItem" + ";" + "$Count"
AC -Value $outinfo -Path ".\Counted OUs.csv"
}

This script contains a replace of dynamic content. You have to be very careful using such a replace. We have CN’s with a value like Berg, Steven van den. That comma is precisely the problem. To display a comma in the DistinguishedName a break is inserted. So the actual value is CN=Berg\, Steven van den,. Based on the specified condition this does not apply. Meaning that you will have inconsistent results. In script 3 this is corrected.

Script 3

IPMO ActiveDirectory
$list = Get-ADUser -Filter {Enabled -eq $True}
$List2 = @()
Foreach ($item in $list){$Result = $item.distinguishedName.Replace('\','').Replace(("CN="+$item.Name+","),'')
$List2 += $Result}
$Uniquelist = $list2 | Sort -Unique
Foreach ($UniqueItem in $Uniquelist){
[int]$Count = 0
Foreach ($Item in $list2){If ($Item -eq $UniqueItem){$Count++}}
$Outinfo = "$UniqueItem" + ";" + "$Count"
AC -Value $outinfo -Path ".\Counted OUs.csv"
}

Here the conditions are met. I have also included an empty array at the line 3. Next all values are added to that array. So only the correct file is exported. Be aware that is you do a double ” after the replace of the special character you’ll need a double backslash. In this case something like .Replace(“\\”,””). The backslash means a break stating a special character.

Even though this script delivers the correct output, I would still advise you to refrain from replacing dynamic content unless you are really sure that every condition is met and that you not filter out too much.

Script 4

IPMO ActiveDirectory
$list = (Get-ADUser -Filter {Enabled -eq $True}).DistinguishedName | %{(GI "AD:\$_").PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')}
$Uniquelist = $list | Sort -Unique
Foreach ($UniqueItem in $Uniquelist){
[int]$Count = 0
Foreach ($Item in $list2){If ($Item -eq $UniqueItem){$Count++}}
$Outinfo = "$UniqueItem" + ";" + "$Count"
AC -Value $outinfo -Path ".\Counted OUs.csv"
}

Script 1 uses the CMDlet Get-Item (Alias GI) to collect the parent OU. In script 1 this is done in separate commands. You can combine this in one line let the example above. I have done this by using the CMDlet Foreach-Object (Alias %).

Script 5

IPMO ActiveDirectory
$list = (Get-ADUser -Filter {Enabled -eq $True}).DistinguishedName | %{(GI "AD:\$_").PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')}
Foreach ($UniqueItem in ($List | Sort -Unique)){
[int]$Count = 0
Foreach ($Item in $list){If ($Item -eq $UniqueItem){$Count++}}
$Outinfo = "$UniqueItem" + ";" + "$Count"
AC -Value $outinfo -Path ".\Counted OUs.csv"
}

Unlike script 4 in shorted the script even further by not generation a separate unique item list.

Script 6

IPMO ActiveDirectory
$list = (Get-ADUser -Filter {Enabled -eq $True}).DistinguishedName | %{(GI "AD:\$_").PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')}
Foreach ($UniqueItem in ($List | Sort -Unique)){
[int]$Count = 0
Foreach ($Item in $list){If ($Item -eq $UniqueItem){$Count++}}
AC -Value ("$UniqueItem" + ";" + "$Count") -Path ".\Counted OUs.csv"
}

The output string is combined in one line.

Script 7

IPMO ActiveDirectory
Get-ADUser -Filter {Enabled -eq $True} | % {($_).distinguishedName.Replace('\','').Replace(("CN="+$_.Name+","),'')} | Group | Select Name,Count | epcsv ".\Counted OUs.csv" -Delimiter ";" -NoTypeInformation

This example is even prettier. Nearly a oneliner. To count we use the Group-Object (Alias Group) te count the number of occurrences. Next we Select the fields we want. Followed by exporting the data by Export-CSV (Alias epcsv). This can be done with that other command aswel.

Script 8

IPMO ActiveDirectory
(Get-ADUser -Filter {Enabled -eq $True}).DistinguishedName | %{(GI "AD:\$_").PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')} | Group | Select Name,Count | epcsv ".\Counted OUs.csv" -Delimiter ";" -NoTypeInformation

In this case you have few ways to select the data. Like these:

IPMO ActiveDirectory
Get-ADUser -Filter {Enabled -eq $True} | %{(GI AD:\$_).PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')} | Group | Select Name,Count | epcsv ".\Counted OUs.csv" -Delimiter ";" -NoTypeInformation

Or

IPMO ActiveDirectory
Get-ADUser -Filter {Enabled -eq $True} | %{(GI AD:\$_).PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')} | Group |  % { AC -Value ("$($_.Name)" + ";" + "$($_.Count)") -Path ".\Counted OUs.csv"}

Or

IPMO ActiveDirectory
Get-ADUser -Filter {Enabled -eq $True} | %{(GI AD:\$_).PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')} | Group  | % { AC -Value ($($_.Name) + ";" +  ($($_).Group.Count)) -Path ".\Counted OUs.csv"}

Except one, these scripts all generate the same output. Know that there are many more ways to get the same output. Again the best method is the one you can remember and you feel comfortable using. If you like oneliners you’ll prefer script 8. If you like to have an overview of what is happening you’ll prefer script 1. Based on your skills in scripting/Powershell you’ll choose anything in between.

You could also have selected CanonicalName. This would have been a lot easier as it doesn’t contain any breaking (\). Another advantage would be that you can sort alphabetically. Since DistinguishedName is formed from the deepest to the highest level is it only possible to sort based on the last OU. Normally you would want to have this export correspond with the hierarchy of Active Directory. This is only possible with CanonicalName. CanonicalName is built up from highest in the hierarchy to the lowest container.

Reference table

Alias CMDlet
IPMO  Import-Module
 GM  Get-Member
 GI  Get-Item
 ?  Where-Object
AC  Add-Content
 GC  Get-Content
 Sort Sort-Object
 % Foreach-Object
 Group  Group-Object
 Select  Select-Object
epcsv  Export-CSV

I hope you’ve had fun!