<#Reset-UserAccountACLs.ps1 This script resets the security (ACLs) for user accounts within an OU to the defaults for a new user in that OU. It works by creating a temporary user object, copying the permissions, and applying them to existing users. You choose the domain and OU from a GUI. Test mode will create a report with no changes made. You should review the permissions of the parent OU before committing changes. Alan Kaplan 10/17/2017 Ver 1.1 10/18/2017 added export of summary to file, GUI logfile picker Ver 1.2 19/18/2017 renamed from Set-DefaultUserPermissions to Reset-UserAccountACLs, and added preserve owner option #> $strVer = '1.2' #Requires -module ActiveDirectory #Ensure the AD drive is available if ($Env:ADPS_LoadDefaultDrive -eq 0){Remove-Module activedirectory -Force;$Env:ADPS_LoadDefaultDrive = 1} Import-Module activedirectory -NoClobber Add-Type -assemblyname Microsoft.visualBasic Function Create-RemoteADDrive{ Param ( # DNS name of domain to connect to [Parameter(Mandatory=$true,Position=0)] $DNSDomain, [Switch]$ConnectToRoot ) #Remove drive if it exists if (Test-Path RemoteAD:) { Pushd c: Remove-PSDrive RemoteAD Popd } $RemoteDomain = Get-ADDomain $DNSDomain $server = ($RemoteDomain).RidMaster if ($ConnectToRoot){ $Root = '' }ELSE{ #Connect to Default Naming Context $Root = ($RemoteDomain).DistinguishedName } #Note Scope is Script. If not set, disappears outside of function. Can set to Global New-PSDrive -Name "RemoteAD" -PSProvider ActiveDirectory -root $Root -server $server -Scope Script } Function Select-ADOU { [CmdletBinding()] Param ( # Title text [Parameter(Position=0)] $TitleText, # Instruction text is below title, and above domain box [Parameter(Position=1)] [string]$InstructionText, # Select Button Text [Parameter(Position=3)] [string]$BtnText, # Show Containers. Default is only OUs [Parameter(Mandatory=$False)] [Switch]$ShowContainers = $False, # Force Single Domain. No navigation within forest [Parameter(Mandatory=$False)] [Switch]$SingleDomain = $False, #Set the initial domain. This must be a FQDN, example Contoso.com [Parameter(Mandatory=$False)] [string]$InitialDomain, #Use Checkboxes [Parameter(Mandatory=$False)] [switch]$ShowCheckBoxes=$False ) ### Helper functions ### Function Show-NoCheck{ #Show the Form without CheckBoxes $dialogResult = $Form.ShowDialog() if ($dialogResult -eq [System.Windows.Forms.DialogResult]::OK) { $RetVal = [PSCustomObject]@{ Domain = [string]$DomainBox.SelectedItem OUName= [string]$treeViewNav.SelectedNode.Text OUDN = [string]$treeViewNav.SelectedNode.Tag } $RetVal $Form.Close() } } #Sapien.com Function Get-CheckedNodes { param( [ValidateNotNull()] [System.Windows.Forms.TreeNodeCollection] $NodeCollection, [ValidateNotNull()] [System.Collections.ArrayList]$CheckedNodes) foreach($Node in $NodeCollection) { if($Node.Checked) { [void]$CheckedNodes.Add($Node) } Get-CheckedNodes $Node.Nodes $CheckedNodes } } Function Show-CheckBoxes{ #Show the Form $dialogResult = $Form.ShowDialog() if ($dialogResult -eq [System.Windows.Forms.DialogResult]::OK){ $CheckedNodes = New-Object System.Collections.ArrayList Get-CheckedNodes $treeViewNav.Nodes $CheckedNodes $RetVal = foreach($node in $CheckedNodes) { [PSCustomObject]@{ Domain = [string]$DomainBox.SelectedItem OUName= $node.Text OUDN = [string]$Node.Tag } } $RetVal $Form.Close() } } Function Check-Domain ($dom){ Try{ [adsi]::Exists("LDAP://$dom") }Catch{ $False } } ### End helper functions### #Default display text if not set by argument if ($TitleText.length -eq 0) { if ($ShowCheckBoxes){ $TitleText = "Select OU(s)" }ELSE{ $TitleText = "Select an OU" } } if ($BtnText.length -eq 0) { if ($ShowCheckBoxes){ $BtnText = "Accept Selected OU(s)" }ELSE{ $BtnText = "Accept Selected OU" } } if ($InstructionText.length -eq 0) { if ($SingleDomain){ $InstructionText = "Double click on a node to expand." }ELSE{ $InstructionText = "Click on domain box after a domain change to show tree. Double click on a node to expand." } } if ($ShowContainers -eq $True){ $LDAPFilter = '(|(objectClass=container)(ObjectCategory=OrganizationalUnit))' }ELSE{ $LDAPFilter = '(ObjectCategory=OrganizationalUnit)' } if ($InitialDomain.Length -eq 0){ $InitialDomain = ([System.DirectoryServices.ActiveDirectory.domain]::GetCurrentDomain()).Name } If ($SingleDomain){ $DomainList = $InitialDomain }ELSE{ $Forest = ([System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()) $DomainList = ($Forest.Domains).name | sort -CaseSensitive } $startNum= $DomainList.IndexOf($InitialDomain) # Import the Assemblies Add-Type -assemblyname System.Windows.Forms Add-Type -assemblyname System.Drawing Add-Type -assemblyname Microsoft.VisualBasic # Form Objects $Form = New-Object System.Windows.Forms.Form $DomainBox = New-Object System.Windows.Forms.ListBox $DomainLbl = New-Object System.Windows.Forms.Label $AcceptBtn = New-Object System.Windows.Forms.Button $treeViewNav = New-Object System.Windows.Forms.TreeView $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState $StatusBar = New-Object System.Windows.Forms.StatusBar $InstructionsLabel = New-Object System.Windows.Forms.Label #Event Script Blocks $DomainBox_SelectedIndexChanged={ $CurrentDomain= $DomainBox.SelectedItem #check communication with selected domain if ((Check-Domain $CurrentDomain) -eq $False){ Write-Warning "$CurrentDomain does not exist or cannot be contacted." Exit } if ($Script:LoadCount -gt 0){ #initial domain is current $domain = [adsi]"LDAP://$CurrentDomain" }ELSE{ $domain=[adsi]'' } #Clear old results $TreeviewNav.Nodes.Clear() $newnode=New-Object System.Windows.Forms.TreeNode $newnode.Name=$domain.Name $newnode.Text=$domain.distinguishedName $newnode.Tag=$domain.distinguishedName $treeviewNav.Nodes.Add($newnode) $Script:LoadCount=$LoadCount+1 #Expand the initial tree Invoke-command $treeviewNav_DoubleClick } $treeviewNav_DoubleClick={ [System.Windows.Forms.Application]::UseWaitCursor = $true $StatusBar.Text = "Getting list, please wait..." if ($treeviewNav.SelectedNode -eq $null){ #For first listing in treeview, select root $node =$treeviewNav.Nodes[0] }Else{ $node=$treeviewNav.SelectedNode } if($node.Nodes.Count -eq 0){ $SearchRoot="LDAP://$($node.Tag)" $ADSearcher = [adsisearcher] '' $ADSearcher.SearchRoot = $SearchRoot $ADSearcher.PageSize = 500 $ADSearcher.SearchScope = "OneLevel" $ADSearcher.CacheResults = $false $ADSearcher.Filter = $LDAPFilter $List = ($ADSearcher.FindAll()).getEnumerator().properties foreach ($OU in $List){ $OUName = $OU.Item("Name") $OUDN = $OU.Item("DistinguishedName") $newnode=New-Object System.Windows.Forms.TreeNode $newnode.Name=$OUName $newnode.Text=$OUName $newnode.Tag=$OUDN $node.Nodes.Add($newnode) } } $node.Expand() [System.Windows.Forms.Application]::UseWaitCursor = $False $statusbar.text = "" } $OnLoadForm_StateCorrection= {#Correct the initial state of the form to prevent the .Net maximized form issue $Form.WindowState = $InitialFormWindowState } $form.ClientSize = New-Object System.Drawing.Size(445,595) $Form.Name = "Form" $Form.Text = $TitleText $InstructionsLabel.Location = New-Object System.Drawing.Point(10,15) $InstructionsLabel.Name = "InstructionsLabel" $InstructionsLabel.Size = New-Object System.Drawing.Size(420,35) $InstructionsLabel.Font = New-Object System.Drawing.Font("Microsoft Sans Serif",8.25,2,3,0) $InstructionsLabel.Text = $InstructionText $Form.Controls.Add($InstructionsLabel) $DomainBox.Name = "DomainBox" $DomainBox.FormattingEnabled = $True $DomainBox.Font = New-Object System.Drawing.Font("Microsoft Sans Serif",9.75,0,3,0) $DomainBox.Location = New-Object System.Drawing.Point(185,50) $DomainBox.Size = New-Object System.Drawing.Size(160,30) $DomainBox.add_SelectedIndexChanged($DomainBox_SelectedIndexChanged) foreach ($Domain in $domainlist){ [void] $DomainBox.Items.Add($domain) } $DomainBox.setSelected($startNum,$true) $Form.Controls.Add($DomainBox) $DomainLbl.Name = "DomainLbl" $DomainLbl.Location = New-Object System.Drawing.Point(20,50) $DomainLbl.Size = New-Object System.Drawing.Size(160,25) $DomainLbl.Text = "Selected Domain" $DomainLbl.TextAlign = "Middleright" $Form.Controls.Add($DomainLbl) $AcceptBtn.Name = "AcceptBtn" $AcceptBtn.Location = New-Object System.Drawing.Point(125,535) $AcceptBtn.Size =New-Object System.Drawing.Size(170,25) $AcceptBtn.Text = $BtnText $AcceptBtn.DialogResult = [System.Windows.Forms.DialogResult]::OK $Form.Controls.Add($AcceptBtn) $form.AcceptButton = $AcceptBtn $treeViewNav.Name = "treeViewNav" $treeViewNav.CheckBoxes = $ShowCheckBoxes $treeViewNav.Location =New-Object System.Drawing.Point(45,80) $treeViewNav.Size =New-Object System.Drawing.Size(325,450) $treeViewNav.add_DoubleClick($treeViewNav_DoubleClick) $treeViewNav.add_AfterSelect($treeViewNav_AfterSelect) $Form.Controls.Add($treeViewNav) $form.StartPosition = "CenterParent" $form.Controls.Add($StatusBar) #Save the initial state of the form $InitialFormWindowState = $Form.WindowState #Init the OnLoad event to correct the initial state of the form $Form.add_Load($OnLoadForm_StateCorrection) if ($ShowCheckBoxes) {Show-CheckBoxes }ELSE{ Show-NoCheck } } #End Function Function Compare-ACL { # based on https://groups.google.com/forum/#!topic/microsoft.public.windows.powershell/LYhxNL4544Y [cmdletbinding()] Param ( [parameter(mandatory=$True)]$ReferenceObject, [parameter(mandatory=$True)]$DifferenceObject ) $ReferenceObjectACL = (($ReferenceObject).AccessToString).Split("`n") $DifferenceObjectACL = (($DifferenceObject).AccessToString).Split("`n") Compare-Object -ReferenceObject $ReferenceObjectACL -DifferenceObject $DifferenceObjectACL| Foreach { #toProper case $item = (Get-Culture).TextInfo.ToTitleCase($_.InputObject).Trim() if($_.SideIndicator -eq '<=') {$WhereFound = 'added;'} if($_.SideIndicator -eq '=>') {$WhereFound = 'removed;'} "$Item ACL $WhereFound" } } Function Get-SaveFile($Prompt, $filter, $initialDirectory,[string]$defaultFile) { add-type -assemblyname System.Windows.Forms $SaveFileDialog = New-Object System.Windows.Forms.SaveFileDialog $SaveFileDialog.initialDirectory = $initialDirectory $SaveFileDialog.filter = $filter $SaveFileDialog.Title = $Prompt $SaveFileDialog.filename = $defaultFile $SaveFileDialog.ShowDialog() | Out-Null $SaveFileDialog.ShowHelp = $true return $SaveFileDialog.filename } #### Script Begins ### #Message for initial menu $msg = "This script resets the security (ACLs) for user accounts within an OU to the defaults for a new user in that OU. "+` "It works by creating a temporary user object, copying its permissions, and applying them to existing users."+` "`fYou will choose the domain and OU from a GUI. Test mode will create a report with no changes made." +` "You should review the permissions of the OU before committing changes." #want line above without line break $msg= $msg.Replace("`n",$null) $msg +="`n`n1) Commit Changes to AD`n2) Run in Test Mode`n0) Quit" $title = "Reset User Security to Default Permissions" [int]$iMenuChoice= [Microsoft.VisualBasic.Interaction]::InputBox($msg,"$title v. $strVer",2) Switch( $iMenuChoice ){ Default {Exit} 1 {$bTest = $False ; Break} 2 {$bTest = $True ;Break} } #Call Select OU $ADObject = Select-ADOU -BtnText "Select Path" -Title "Fix ACLs for Users in Selected OU" if ($adObject -eq $null){Exit} $TargetOU = $ADObject.OUDN [string]$Domain = $ADObject.domain $dom = ($Domain.Split(".")[0]).ToUpper() $ForestName = [string](get-adforest).name $domainDN = [string](Get-ADDomain $domain).distinguishedName $SB = $targetOU.Replace(",$domaindn",'') $msg = 'Do you want to preserve the original creator/owner information? If no, the creator owner will be you.' $btn = 'YesNoCancel,defaultbutton1,Question' $retval = [Microsoft.VisualBasic.Interaction]::MsgBox($msg,$btn, "Preserve Owner?") switch ($Retval) { 'Yes' {$bPreserveOwner = $True} 'No'{$bPreserveOwner = $False} Default {Exit} } #Logfile $ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path $logfile = "$dom " + ($ADObject).OUName +' ACLs '+$(Get-Date).ToString("yyyyMMdd")+'.csv' #Splatting Get-SaveFile parameters $params = @{ Prompt = "Select name and path for CSV log file" filter = "CSV Files (*.csv)|*.csv" InitialDirectory = $ScriptPath DefaultFile = $logfile } $LogFile = Get-SaveFile @params if ($LogFile -eq '' ){Exit} #Temp User settings #Use GUID for complex password, disabled, smartCard required $tempPW = [string](([guid]::NewGuid()).Guid) $SecurePW= ConvertTo-SecureString $tempPW -AsPlainText -force #Splat for additional properties #You can change any of these $params =@{ Name = "Default Permissions" Enabled = $false SamAccountName = 'TempAcctPerms' UserPrincipalName = "Temp.AcctPerms`@$forestName" Description = "TEMP: Created by $env:userName to get default user perms" AccountPassword = $SecurePW Smartcardlogon = $true } Write-Progress "Creating Temporary User account within $TargetOU" Try{ $TempUser = New-ADUser -Server $domain -path $targetOU @Params -PassThru -ErrorAction Stop } Catch { Write-Warning "You must have rights to create a new user in $targetOU to run this [even in test mode]" } $TempUserName = ($TempUser).name $uDN = ($TempUser.DistinguishedName).Replace(",$domaindn",'') Create-RemoteADDrive $domain | Out-Null do { Write-Progress "Pausing for temporary user account to replicate.." sleep -Milliseconds 500 } while ((Test-Path "RemoteAD:\$udn") -eq $false) #Get GoodACLs from new temp user $GoodACLs = Get-Acl -Path "RemoteAD:\$udn" $results = Get-ADUser -SearchBase $targetOU -Server $domain -Filter * -Properties description | Sort Name | where { $_.Name -ne $TempUserName } | Foreach{ $uDN = ($_.DistinguishedName).Replace(",$domaindn",'') Write-Progress "Checking ACLS on $udn" Try{ $acls = Get-Acl -Path "RemoteAD:\$udn" $comp = Compare-ACL -ReferenceObject $GoodACLs -DifferenceObject $acls $Owner = $($acls).Owner if ($comp) { Write-Progress "Setting ACLS on $udn" #Reset ACL to match newly created user Try{ $NewACL = Set-Acl -Path "RemoteAD:\$udn" -AclObject $GoodACLs -WhatIf:$btest -Passthru -ErrorAction Stop #Preserve owner information if (($Owner -notmatch 'Domain Admins') -and ($bPreserveOwner -eq $true)){ $d = $owner.Split("\")[0] $o = $owner.Split("\")[1] #Preserve owner/creator $objOwner = New-Object System.Security.Principal.NTAccount($d, $o) $NewACL.SetOwner($objOwner) Set-Acl -Path "RemoteAD:\$udn" -AclObject $NewACL -WhatIf:$btest -ErrorAction Stop } $retval = "Success" } Catch { $retval = "Failed. " + $error[0].Exception.Message } #cleanup list of changes $comp = [string]$comp -replace ".$" } ELSE { $retval = 'Skipped. Security okay' } } Catch { $retval = $error[0].Exception.message } if ($btest){$retval = 'Test mode only - no changes made.'} [PSCustomObject]@{ Domain = $Domain Name = $_.name SamAccountName = $_.SamAccountName Owner = $Owner Description = $_.Description Result = $retval Changes = [string]$comp DistinguishedName = $_.DistinguishedName } } Write-Progress "Done" -Completed #Delete temp AD User Get-ADUser $TempUser | Remove-ADObject -Confirm:$false $results | export-csv $logfile -NoTypeInformation Write-host -ForegroundColor Gray "Done. Log is $logfile" #Show Summary of Changes $SumData = ($results.changes) -split ";" | Where {$_ } |Foreach {$_.trim()} |Sort -Property Count | Group-Object -NoElement $title = "Change Summary: Selected items will be saved to a file when [OK] is pressed" $retval = $SumData | Out-GridView -Title $title -PassThru if ($retval){ $sumLogFile = $logfile.Replace('.',' Summary.') $retval | Select Count, name | sort count| Export-Csv $sumLogFile -NoTypeInformation Write-host -ForegroundColor Gray "Summary log is $sumLogFile" }