by David | Oct 16, 2020 | Information Technology, PowerShell, Resources
This little script will allow you to check a folder location, if that folder location has x number of files, it will restart the service of your choice on a target computer. Then it will log that information and finally it will email someone this information.
function Invoke-FileServiceIntegrityCheck {
<#
.SYNOPSIS
Checks a file path for number of files. If greater, restarts a service and emails a message and logs a log.
.DESCRIPTION
Checks a file path for a set number of files that are predefined by the end user. If the file count is greater, it will restart a service. It will log the results and email a person stated by the end user.
.PARAMETER SearchPath
Target Path to scan for file count.
.PARAMETER FileCount
File count that will be used to determine the request size.
.PARAMETER ComputerName
Target Computer Name that the service lives on.
.PARAMETER ServiceName
Name of the service to restart.
.PARAMETER SendReportTo
The email address to which the email report will be sent to.
.PARAMETER EmailDomain
The domain name. For example, example.local
.PARAMETER MailServer
The mail server to send the email from.
.PARAMETER Recurse
Recurses the file search path.
.PARAMETER Credential
The Credentials, if needed for the email server.
.EXAMPLE
Invoke-FileServiceIntegrityCheck -SearchPath \\server1\share -FileCount 25 -ComputerName server2 -ServiceName intergrator -SendReportTo bob@example.com -EmailDomain example.com -Mailserver mail.example.com -Credential (Get-Credential)
Checks the folder \\server1\share to see if there is 25 or more files. If it does, it will restart the service named intergrator on server 2.
Then it will send an bob@example.com from watcheye@example.com using mail.example.com and the credentials provided.
.EXAMPLE
Invoke-FileServiceIntegrityCheck -SearchPath \\server1\share -FileCount 25 -ComputerName server2 -ServiceName intergrator -SendReportTo bob@example.com -EmailDomain example.com -Mailserver mail.example.com
Checks the folder \\server1\share to see if there is 25 or more files. If it does, it will restart the service named intergrator on server 2.
Then it will send an bob@example.com from watcheye@example.com using mail.example.com. Email will be sent from the server. If no relay is setup, the server will reject the email.
.INPUTS
[none]
.OUTPUTS
email and log
.NOTES
Author: David Bolding
Date: 10/14/2020
.LINK
https://github.com/rndadhdman/PS_Super_Helpdesk
#>
[cmdletbinding()]
param (
[parameter(HelpMessage = "Folder Search Path", Mandatory = $True)][string]$SearchPath,
[parameter(HelpMessage = "File Count", Mandatory = $True)][int]$FileCount,
[parameter(HelpMessage = "Computer That the server Lives", Mandatory = $True)][String]$ComputerName,
[parameter(HelpMessage = "Service Name", Mandatory = $True)][String]$ServiceName,
[parameter(HelpMessage = "Service Name", Mandatory = $True)][String]$SendReportTo,
[parameter(HelpMessage = "Service Name", Mandatory = $True)][String]$EmailDomain,
[parameter(HelpMessage = "Service Name", Mandatory = $True)][String]$Mailserver,
[parameter(HelpMessage = "Recurse the Search")][switch]$Recurse
[Parameter(HelpMessage = "Allows for custom Credential.")][System.Management.Automation.PSCredential]$Credential
)
#Checks Files
if ($Recurse) { $Files = Get-ChildItem "$SearchPath" -Recurse } else { $Files = Get-ChildItem "$SearchPath" }
if ($Files.Count -ge $FileCount) {
$StopTime = Get-date
Get-Service -ComputerName mtxs-lifetecapp -Name "Interlink" | Stop-Service
sleep 30
Get-Service -ComputerName mtxs-lifetecapp -Name "Interlink" | Start-Service
$StartTime = Get-date
sleep 30
if ($Recurse) { $Files2 = Get-ChildItem "$SearchPath" -Recurse } else { $Files2 = Get-ChildItem "$SearchPath" }
if (!(Test-Path c:\logs)) {mkdir c:\logs}
#Gathers Log Info
$Log = [pscustomobject]@{
ComputerName = $ComputerName
ServiceName = $ServiceName
SearchPath = $SearchPath
StopFileCount = $Files.count
ServiceStopTime = $StopTime.ToString()
ServiceStartTime = $StartTime.tostring()
StartFileCount = $Files2.count
CallingComputer = $env:COMPUTERNAME
}
$Log | Export-csv c:\temp\Invoke_FileServiceIntegrityCheck_log.csv -Append
#Builds EMail
$ErrorHtmlbody = @"
<html>
<head>
<style>
body {
Color: #252525;
font-family: Verdana,Arial;
font-size:11pt;
}
h1 {
text-align: center;
color:#C8102E;
Font-size: 34pt;
font-family: Verdana, Arial;
}
</style>
</head>
<body>
<h1>$ServiceName Restarted</h1>
<hr>
<br>
Dear Watcher,<br>
<br>
During my last check there were <b>$($Files.Count)</b> files inside <i>$SearchPath</i>. The Integrator was stopped on <b>$($StopTime.tostring())</b> and started on <b>$($StartTime.tostring())</b>. There are now <b>$($files2.count)</b> in <i>$SearchPath</i>. Please investigate on why the $ServiceName failed on $ComputerName.<br>
<br>
<br>
Thank you,<br>
<i>$ServiceName WatchEye</i><br>
<br>
<hr>
</body>
</html>
"@
if ($PSBoundParameters.ContainsKey('Credential')) {
Send-MailMessage -To $SendReportTo -From "Service Watch Eye <WatchEye@$EmailDomain>" -Subject "$ServiceName Failure" -BodyAsHtml $ErrorHtmlbody -SmtpServer $Mailserver -Credential $Credential
} else {
Send-MailMessage -To $SendReportTo -From "Service Watch Eye <WatchEye@$EmailDomain>" -Subject "$ServiceName Failure" -BodyAsHtml $ErrorHtmlbody -SmtpServer $Mailserver
}
}
}
Example
Invoke-FileServiceIntegrityCheck -SearchPath \\server1\share -FileCount 25 -ComputerName server2 -ServiceName integrator -SendReportTo bob@example.com -EmailDomain example.com -Mailserver mail.example.com
In this example, we will search for the share on server1 if it has more than 25 files. If it does, then we will restart the integrator on server2. Finally, we will email bob@example.com with an email from watcher@example.com using the mail server mail.example.com.
by David | Oct 12, 2020 | Help Desk, Information Technology, PowerShell
Often times I am asked to give user a the same rights as user b. Since everything is setup with groups, this is easy to do. All I have to do is copy all the security groups to the new users. Normally I would use the command Get-ADPrincipalGroupMembership whoever, that has become less trust worthy over time for us. So I made a different approach.
$Groups = (Get-ADUser -Identity $SourceUser -Properties memberof).memberof -Replace '^cn=([^,]+).+$', '$1' | Sort-Object | ForEach-Object { (Get-ADGroup -Filter "name -like '$_'")}
The above command grabs the source users member of and filters it out for just the name. Then it loops through each and gets the group name. From here we can filter even farther to get the security and then loop those and added the single member making a one liner, but I want this thing to be bigger and better. A good function that can do distribution, security, universal, global and domain local groups to more than one single user with credentials or not. I want this thing to be nice. Here is the heart beat of the command:
foreach ($Target in $TargetUser) {
$Groups | ForEach-Object {
try {
if ($PSBoundParameters.ContainsKey('Credential')) {
Write-Verbose "Add $($_.Samaccountname) a $($_.GroupCategory.tostring()) a $($_.GroupScope.tostring()) to $Target"
Add-ADGroupMember -Identity $_.samaccountname -Members $Target -Credential $Credential
} else {
Write-Verbose "Add $($_.Samaccountname) a $($_.GroupCategory.tostring()) a $($_.GroupScope.tostring()) to $Target"
Add-ADGroupMember -Identity $_.samaccountname -Members $Target
}
} catch {
Write-Verbose "Failed to add $($_.Samaccountname) to $Target"
Write-Warning "$_ could not apply to $Target"
}
}
}
We target each user with this information. Then we add the group accordingly. So, how do you split the Security group vs Distribution group? We do this by using PSboundparameters and a Where-object group. So we declare a parameter in the parameter area and then ask if it is being used. If it is, we use the where object to search for the groupcategory.
$Groups = (Get-ADUser -Identity $SourceUser -Properties memberof).memberof -Replace '^cn=([^,]+).+$', '$1' | Sort-Object | ForEach-Object { (Get-ADGroup -Filter "name -like '$_'")}
if ($PSBoundParameters.ContainsKey('GroupCategory')) {$Groups = $Groups | Where-Object {$_.GroupCategory -like "$GroupCategory"}}
if ($PSBoundParameters.ContainsKey('GroupScope')) {$Groups = $Groups | Where-Object {$_.GroupScope -like "$GroupScope"}}
Lets put it all together shall we, so you can get back on with your day.
Function Copy-SHDUserGroupToUser {
[cmdletbinding()]
param (
[Parameter(
ValueFromPipeline = $True,
ValueFromPipelineByPropertyName = $True,
HelpMessage = "Enter a users Name",
Mandatory = $true)][String]$SourceUser,
[Parameter(HelpMessage = "Target User", Mandatory = $True)][string[]]$TargetUser,
[parameter(Helpmessage = "Group Category")][validateset("Security","Distribution")]$GroupCategory,
[parameter(Helpmessage = "Group Scope")][validateset("Universal","Global","DomainLocal")]$GroupScope,
[Parameter(HelpMessage = "Allows for custom Credential.")][System.Management.Automation.PSCredential]$Credential
)
$Groups = (Get-ADUser -Identity $SourceUser -Properties memberof).memberof -Replace '^cn=([^,]+).+$', '$1' | Sort-Object | ForEach-Object { (Get-ADGroup -Filter "name -like '$_'")}
if ($PSBoundParameters.ContainsKey('GroupCategory')) {$Groups = $Groups | Where-Object {$_.GroupCategory -like "$GroupCategory"}}
if ($PSBoundParameters.ContainsKey('GroupScope')) {$Groups = $Groups | Where-Object {$_.GroupScope -like "$GroupScope"}}
foreach ($Target in $TargetUser) {
$Groups | ForEach-Object {
try {
if ($PSBoundParameters.ContainsKey('Credential')) {
Write-Verbose "Add $($_.Samaccountname) a $($_.GroupCategory.tostring()) a $($_.GroupScope.tostring()) to $Target"
Add-ADGroupMember -Identity $_.samaccountname -Members $Target -Credential $Credential
} else {
Write-Verbose "Add $($_.Samaccountname) a $($_.GroupCategory.tostring()) a $($_.GroupScope.tostring()) to $Target"
Add-ADGroupMember -Identity $_.samaccountname -Members $Target
}
} catch {
Write-Verbose "Failed to add $($_.Samaccountname) to $Target"
Write-Warning "$_ could not apply to $Target"
}
}
}
}
Examples
Copy-SHDUserGroupToUser -SourceUser bobtheuser -TargetUser test1,test2,test3 -GroupCategory Security -GroupScope Global -Verbose
This will add all of bobtheuser’s global security groups to test1, test2, and test3 users and verbose the info out.
Copy-SHDUserGroupToUser -SourceUser bobtheuser -TargetUser test1,test2,test3
This will add all the bobtheuser’s groups to test1, test2, and test3.
by David | Oct 11, 2020 | Help Desk, Information Technology, PowerShell
Have you ever had trouble getting lockout information for a single user? The DCs have the logs, but getting them can be tough. The core idea is to use the Get-Winevent command to gather the logs needed. Doing this from a remote computer is time-consuming. However, using the Invoke-Command tends to speed things up. Inside this example, I am going to use something called Splatting. Splatting allows you to drop everything a command needs into a command quickly and easily by creating a hash table. Now, not all commands like splatting, so be aware of that. Here is the heart of the code:
$parameters = @{
ComputerName = $ComputerName
ScriptBlock = {
Param ($param1)
$FilterTable = @{
'StartTime' = $param1
'EndTime' = (Get-date)
'LogName' = 'Security'
'Id' = 4740
}
$Events = Get-WinEvent -FilterHashtable $FilterTable
foreach ($E in $Events) {
[pscustomobject]@{
Time = $E.TimeCreated
ID = $E.ID
DC = $E.Properties[4].value
SID = $E.Properties[2].value
Domain = $E.Properties[5].Value
Username = $E.Properties[0].value
Computer = $E.Properties[1].value
}
}
}
ArgumentList = $time
}
Invoke-command @Parameters
The first thing we are doing is creating the parameters that will be splatted. Just like a hashtable, it’s something = something. So the computer name is the Array computername. The computer name inside the Invoke-command can handle arrays. Making this much easier as we want the DCs.
Next is the script block. This is where the invoke-command will be executing everything from. We start off with a Param () block. It will get it’s input from the Argumentlist later. The Argumentlist is the variables we will be dropping into the command. In this case, it will be time. As in this script you can build your time based on how far back you want to search your logs.
Next is the filter table. The Get-winevent command likes hashtables. So we start off with our starttime, basically how far back we are going to go. Then the end time which is the time of execution. The Logname is the type, there is an application, security, system, and more. Since the lockouts are security, we choose security. Finally, we want the ID. For account lockouts, the ID is 4740. There is an online Encyclopedia for windows logs. (Link)
The Get-WinEvent command is followed by the FilterHashtable flag. We dump this into the events variable for sort throu. Now we search each Event in Events. We express this with an E because $event is a system variable and we don’t want to cause any issues. We want the time it was created. The ID, what DC it came from. The SID of the user. The Domain. The username of the user, and finally we want the calling workstation. Once all that is put together we splat it into the command Invoke-Command.
That is the heart, but not the power. This script allows you to call back from the day, hour, and minute back. If you don’t put in a input, it makes it a day for you. It also can target a single user or different computers. Finally it gives you the ability to use different credentials. Lets take a look at the parameters.
param (
[parameter(Helpmessage = "Username of target")][Alias('User', 'Samaccountname')][String[]]$Username,
[parameter(Helpmessage = "Days back")][int]$Day,
[parameter(Helpmessage = "Days back")][int]$Hour,
[parameter(Helpmessage = "Days back")][int]$Minute,
[parameter(Helpmessage = "Computers to target, default is domain controllers.")][Alias('Computer', 'PC')][String[]]$ComputerName = (Get-ADDomain).ReplicaDirectoryServers,
[Parameter(HelpMessage = "Allows for custom Credential.")][System.Management.Automation.PSCredential]$Credential
)
We first are looking for a list of usernames. Notice we don’t have to declare this variable. So, if we just want all the lockouts, we just don’t delcare it. This is done by using the PSBoundParameters.ContainsKey option. So, if the script sees the username is being used, it will follow this command.
if ($PSBoundParameters.ContainsKey('Username')) {
foreach ($user in $Username) {
$Return | Where-Object { $_.Username -like "$user" } | Sort-Object Time | Select-Object Username, Domain, SID, Time, Computer, PSComputerName
}
}
The next parameters are also optional. It is days, hour, and minutes back. So, you can state 1 day, 3 hours, and 2 minutes ago and it will find the times during starting at that moment forwarder. This is good if you plan to use this script as an auditor (what it was built for). How this is achived once again is the use of psboundparameters.containskey.
$time = Get-date
if ($PSBoundParameters.ContainsKey('Day')) { $time = ($time.AddDays( - ($day))) }
if ($PSBoundParameters.ContainsKey('Hour')) { $time = ($time.AddHours( - ($Hour))) }
if ($PSBoundParameters.ContainsKey('Minute')) { $time = ($time.AddMinutes( - ($Minute))) }
if (!($PSBoundParameters.ContainsKey('Day')) -and !($PSBoundParameters.ContainsKey('Hour')) -and !($PSBoundParameters.ContainsKey('Minute'))) {
$time = $time.AddDays( - (1))
}
We first declare the time with the current time/date. Then based on the input, we remove days, hours, or minutes from the current time. If there is no input, we tell time to remove 1 day. Depending on the size of your organization and how many logs you have, this can be useful. The $time will be used in the argumentlist.
Now we have the Computername parameter. If you have differentiating computers than your DCs that handles these logs, you can target them with this command. Otherwise, we grab the dc information with a single line of code.
(Get-ADDomain).ReplicaDirectoryServers
Finally we have the ability to add the credentials. Once you delcare the credentials, we add the credential flag to our splat. To do this we create a hashtable with a single item and add it to the parameter.
if ($PSBoundParameters.ContainsKey('Credential')) {
$parameters += @{Credential = $Credential }
}
Now, lets put all this together in a single script so you can copy it and move along with your day.
Function Get-SHDLockoutInfo {
[cmdletbinding()]
param (
[parameter(Helpmessage = "Username of target")][Alias('User', 'Samaccountname')][String[]]$Username,
[parameter(Helpmessage = "Days back")][int]$Day,
[parameter(Helpmessage = "Days back")][int]$Hour,
[parameter(Helpmessage = "Days back")][int]$Minute,
[parameter(Helpmessage = "Computers to target, default is domain controllers.")][Alias('Computer', 'PC')][String[]]$ComputerName = (Get-ADDomain).ReplicaDirectoryServers,
[Parameter(HelpMessage = "Allows for custom Credential.")][System.Management.Automation.PSCredential]$Credential
)
$time = Get-date
if ($PSBoundParameters.ContainsKey('Day')) { $time = ($time.AddDays( - ($day))) }
if ($PSBoundParameters.ContainsKey('Hour')) { $time = ($time.AddHours( - ($Hour))) }
if ($PSBoundParameters.ContainsKey('Minute')) { $time = ($time.AddMinutes( - ($Minute))) }
if (!($PSBoundParameters.ContainsKey('Day')) -and !($PSBoundParameters.ContainsKey('Hour')) -and !($PSBoundParameters.ContainsKey('Minute'))) {
$time = $time.AddDays( - (1))
}
$parameters = @{
ComputerName = $ComputerName
ScriptBlock = {
Param ($param1)
$FilterTable = @{
'StartTime' = $param1
'EndTime' = (Get-date)
'LogName' = 'Security'
'Id' = 4740
}
$Events = Get-WinEvent -FilterHashtable $FilterTable
foreach ($E in $Events) {
[pscustomobject]@{
Time = $E.TimeCreated
ID = $E.ID
DC = $E.Properties[4].value
SID = $E.Properties[2].value
Domain = $E.Properties[5].Value
Username = $E.Properties[0].value
Computer = $E.Properties[1].value
}
}
}
ArgumentList = $time
}
if ($PSBoundParameters.ContainsKey('Credential')) {
$parameters += @{Credential = $Credential }
}
$Return = Invoke-Command @parameters
if ($PSBoundParameters.ContainsKey('Username')) {
foreach ($user in $Username) {
$Return | Where-Object { $_.Username -like "$user" } | Sort-Object Time | Select-Object Username, Domain, SID, Time, Computer, PSComputerName
}
}
else {
$Return | Sort-Object UserName | Select-Object Username, Domain, SID, Time, Computer, PSComputerName
}
}
I hope you enjoy this little script and I hope it helps you grow your company. If you find any bugs, let me know. Thank you so much and have a great day!
by David | Sep 13, 2020 | Help Desk, Information Technology, PowerShell
Now we have a way to find the disable OU, and to disable a user, now it’s time to disable old accounts. We do this by targeting the Last Logon dates.
Word of warning before we continue. When you do this, target a single OU instead of all of the company. Target only enabled users as well. Targeting all of AD will cause you to disable service accounts and system accounts. This can cause your system to crash. Also, target enabled users as many companies us templates that are disabled.
Now we got the warning out of the way, lets get started. The code is very simple. The first step is to get the days back you want to go and find that date/time. We are going to us Get-Date and your input which we are calling $DaysBack.
$Time = (get-date).AddDays(-($DaysBack))
We get the Current date. Then we want to add days to that date, but backwards. So the (Get-Date) gives us the current date. Then .AddDays() will add the days. To make $DaysBack Negative, we simple multiple it by -1. if you completed algebra 1 in high school, you already see it. -($DaysBack) is the multiplication $DaysBack * -1.
Now we have the date we want to search the OU in question and get the users that haven’t log on since $DaysBack. We will do this with the simple Get-ADuser and the Where-Object commands.
$users = Get-aduser -Filter { Enabled -eq $true } -SearchBase $SearchOU -Properties LastLogondate | Where-Object {($_.LastLogonDate -le $Time) -and ($null -ne $_.LastLogonDate)}
We use the get-aduser filter to filter out everything that is enabled. Then we use the search base to search the OU we want to pull the information from. This saves us heartache of “oh crap I disabled my bosses test user.” Next, we pass it through the Where-object. We filter out everyone who last logon date that is less than or equal to the time we set earlier. We also search if nothing is not equal to the last logon date. We do this part because if an account hasn’t logged into the system yet, it will appear. Doing this avoids removing new hires on accident. Another note on the $null -ne $_.lastlogondate, it’s faster to search like this instead of $_.lastlogondate -ne $null. I don’t know why, but it is much faster.
Now we have a list of users who we need to disable, we just start up a for loop and disable them using the Disable-SHDUser.
foreach ($User in $users) {
Disable-SHDUser -Username $user.samaccountname -OU $DisabledOU
}
After the loop is done, it’s done. They are all disabled. But David, I want to see who was disabled! I agree. So I added a simple ShowResults switch for this loop. This is for those who need to see the results.
foreach ($User in $users) {
Disable-SHDUser -Username $user.samaccountname -OU $DisabledOU
if ($ShowResults) {
Get-aduser -Identity $user.samaccountname -Properties LastLogonDate,Memberof | Select-Object DistinguishedName,Samaccountname,Name,Lastlogondate,Enabled,@{l="GroupCount";e={$_.memberof.count}}
}
}
Combining functions in a module is a great way to get things done. These last two blogs should have shown this to you. Now, lets look at the script itself.
The Script
function invoke-SHDDisableInactiveUsers {
[cmdletbinding()]
param (
[parameter(HelpMessage = "Days after", Mandatory = $True)][int]$DaysBack,
[parameter(HelpMessage = "Moves to this OU if provided, If not, finds disable ou and moves it.")][string]$DisabledOU,
[parameter(HelpMessage = "Moves to this OU if provided, If not, finds disable ou and moves it.", Mandatory = $true)][string]$SearchOU,
[Parameter(HelpMessage = "Allows for custom Credential.")][System.Management.Automation.PSCredential]$Credential,
[parameter(HelpMessage = "Show results")][switch]$ShowResults
)
$Time = (get-date).AddDays(-($DaysBack))
if ($PSBoundParameters.ContainsKey('Credential')) {
$users = Get-aduser -Filter { Enabled -eq $true } -Credential $Credential -SearchBase $SearchOU -Properties LastLogondate | Where-Object {($_.LastLogonDate -le $Time) -and ($null -ne $_.LastLogonDate)} | Sort-Object Samaccountname
if ($PSBoundParameters.ContainsKey('DisabledOU')) {
$DisabledOU = $DisabledOU
}
else {
$DisabledOU = Find-SHDDisabledUsersOU -Credential $Credential
}
foreach ($User in $users) {
Disable-SHDUser -Username $user.samaccountname -OU $DisabledOU -Credential $Credential
if ($ShowResults) {
Get-aduser -Identity $user.samaccountname -Properties LastLogonDate,Memberof -Credential $Credential | Select-Object DistinguishedName,Samaccountname,Name,Lastlogondate,Enabled,@{l="GroupCount";e={$_.memberof.count}}
}
}
}
else {
$users = Get-aduser -Filter { Enabled -eq $true } -SearchBase $SearchOU -Properties LastLogondate | Where-Object {($_.LastLogonDate -le $Time) -and ($null -ne $_.LastLogonDate)} | sort-object Samaccountname
if ($PSBoundParameters.ContainsKey('OU')) {
$DisabledOU = $DisabledOU
}
else {
$DisabledOU = Find-SHDDisabledUsersOU
}
foreach ($User in $users) {
Disable-SHDUser -Username $user.samaccountname -OU $DisabledOU
if ($ShowResults) {
Get-aduser -Identity $user.samaccountname -Properties LastLogonDate,Memberof | Select-Object DistinguishedName,Samaccountname,Name,Lastlogondate,Enabled,@{l="GroupCount";e={$_.memberof.count}}
}
}
}
}
Next time I will explain the PSBoundParameters that you see in almost all scripts I run. It gives me the ability to give you the ability to have credentials but not require it.
If you have any questions, feel free to email me. These functions can be found on my git hub.
by David | Sep 12, 2020 | Help Desk, Information Technology, PowerShell
Have you ever started in a company and there was no documentation? The disabled OU isn’t named “Disabled Users” and things are just what the heck? This powershell script will help find that disabled user OU. Believe it or not, it’s a one liner.
((Get-ADUser -filter { enabled -eq $false }).Distinguishedname -replace '^CN=.*?,', '' | Group-Object | Sort-Object -Property Count -Descending | Select-Object -First 1).name
Lets tare this bad boy apart. First we have a Get-Aduser -filter { Enabled -eq $False}. So we want all the users in the company who are disabled. From there we are selecting only the DistinguishedName. We want to remove the first part of the DistinguishedName with a replace command. The Regex is ^CN=.*?,’,” Lets break this down.
.Distinguishedname -replace '^CN=.*?,', ''
^CN= tells us we are looking for the first “CN=” inside this string. Then we ask for the wild cards up to the first , with .*?. We tell it to replace it with nothing, aka double single quotes, ”.
Now this gives us all the OUs that everyone who is disabled lives in. Next we group them together with Group-Object. Group-object is going to give us a clean count of each OU and how many unique items there are for each OU.
| Sort-Object -Property Count -Descending
Next we want to organize everything with a Sort-Object. We select the count property and put it in descending order. This way we can select the first one in the final piece of the puzzle.
| Select-Object -First 1).name
Now we use the Select-object -First 1 command to get the first object from the descending list. This will give you the highest disabled users counted OU.
The Script
function Find-SHDDisabledUsersOU {
[cmdletbinding()]
param (
[Parameter(HelpMessage = "Allows for custom Credential.")][System.Management.Automation.PSCredential]$Credential
)
if ($PSBoundParameters.ContainsKey('Credential')) {
((Get-ADUser -filter { enabled -eq $false } -Credential $Credential).Distinguishedname -replace '^CN=.*?,', '' | Group-Object | Sort-Object -Property Count -Descending | Select-Object -First 1).name
}
else {
((Get-ADUser -filter { enabled -eq $false }).Distinguishedname -replace '^CN=.*?,', '' | Group-Object | Sort-Object -Property Count -Descending | Select-Object -First 1).name
}
}