#Import-SelectedOUPermissions.ps1 #Apply delegations from Export-SelectedOUPermissions.ps1 file #alan dot kaplan at va dot gov #04/06/2017 version 1.0 #04/12/2017 version 1.1. Updated Select-ADOU #06/01/2017 version 1.2 Users and Groups are now supported security principals # Added minimize and restore of ISE, progress indicator #06/02/2017 Improved error message when group not found #05/24/2017 Discovered scrolling may make completion notification scroll off screen, # so added msgbox when script is done. $ver = "1.4" #Requires -version 3 #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 Minimize-Self{ Write-Verbose "Minimizing ISE to ensure you see menus" #http://stackoverflow.com/questions/16552801/how-do-i-conditionally-add-a-class-with-add-type-typedefinition-if-it-isnt-add if (-not ([System.Management.Automation.PSTypeName]'ShoWinAsync').Type){ #Based on Add-Type Help example $Sig = '[DllImport("user32.dll")]public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);' $script:shoWinAsync = Add-Type -MemberDefinition $Sig -Name "Win32ShowWindowAsync" -Namespace Win32Functions -PassThru } $shoWinAsync::ShowWindowAsync((Get-Process -Id $pid).MainWindowHandle, 2) |out-null } Function Restore-Self{ if ($Host.name -match 'ISE'){ #Restore minimized ISE Write-Verbose "Restoring ISE" $shoWinAsync::ShowWindowAsync((Get-Process -Id $pid).MainWindowHandle, 1) |out-null } } Function Get-OpenFile($title, $filter,$initialDirectory) { add-type -assemblyname System.Windows.Forms $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog $OpenFileDialog.initialDirectory = $initialDirectory $OpenFileDialog.filter = $filter $OpenFileDialog.Title = $title $OpenFileDialog.ShowDialog() | Out-Null $OpenFileDialog.filename $OpenFileDialog.ShowHelp = $true } Function Select-ADOU { [CmdletBinding()] Param ( # Title text [Parameter(Position=0)] $Title, # 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, see notes 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." Restore-Self 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.TopMost = $True $form.TopLevel $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 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:){ SL c: ; Remove-PSDrive RemoteAD } $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 -GlobalCatalog| Out-Null } if ($Host.name -match 'ISE'){Minimize-Self} # Script Begins $title= "Import OU Permissions, ver. $ver Alan Kaplan" $menu = @" This imports permissions from a file created by Export-SelectedOUPermissions to selected OUs within a domain. 1) Run in Test Mode 2) Commit changes in AD 0) Quit "@ $retval = [Microsoft.VisualBasic.Interaction]::InputBox($menu, $title, 0) switch ($Retval) { 1 {$btest = $True} 2 {$btest = $false} Default {Restore-Self;Exit} } $ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path $title= "Choose a CSV file created by export script" $ACLList = Get-OpenFile -title $title -filter "CSV Files (*.csv)|*.csv" -initialDirectory $ScriptPath if ($ACLList -eq $null){Restore-Self;exit} $title= "Select OU(s)" $msg = "Check all of OUs you want to get the new delegations. Click on domain box after a domain change to show tree, double click on a node to expand." $ADObjects = Select-ADOU -ShowCheckBoxes -InstructionText $msg if ($OUs -eq ''){Restore-Self;Exit} $OUs = ($ADObjects).OUDN if ($Adobjects.domain.count -eq 1){ $Domain = $ADObjects.domain }ELSE{ $Domain = $ADObjects.domain[0] } Create-RemoteADDrive $Domain -ConnectToRoot $ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path $logfile = "$scriptpath\" + ($Domain).Replace(".","_") +'_Imported_OU_Permissions_'+$(Get-Date).ToString("yyyyMMdd")+'.csv' $LogFile = [Microsoft.VisualBasic.Interaction]::InputBox("Path to log file ", "Log File", $LogFile) Restore-Self if ($LogFile -eq '' ){Exit} $ACLData = Import-Csv $ACLList #If only one Identity Reference, give admin opportunity to review and change $IDR = $ACLData | select -ExpandProperty identityreference -Unique if ($IDR.count -eq 1){ $IDR = [Microsoft.VisualBasic.Interaction]::InputBox("Confirm Delegate Identity", "Delegate To", $IDR) if ($IDR -eq '' ){Exit} #Test user exists $t = ($IDR).split("\") $PrincipalDom = $t[0] $PrincipalName = $t[1] Try { $retval = Get-ADObject -Server $PrincipalDom -Filter "Name -eq '$PrincipalName'" -ErrorAction Stop }Catch{ Write-Warning $Error[0].Exception.Message Exit } #Update to new Identity Reference $ACLData | %{$_.identityreference = $IDR} } $data = foreach ($OU in $OUs){ $ACLData | Foreach -begin { $i=0 if ($btest){$msg = 'Simulating Applying ACLS'}ELSE{$msg = 'Applying ACLs'} $acl = get-acl RemoteAD:\$ou } -Process{ $IDR = [string] $_.IdentityReference Write-Progress -Activity $msg -Status "$ou - $IDR, ACL $i" $t = ($IDR).split("\") $PrincipalDom = $t[0] $PrincipalName = $t[1] #group $Principal = Get-ADObject -Server $PrincipalDom -Filter "Name -eq '$PrincipalName'" -Properties objectSid #User account if ($Principal -eq $null){$Principal = Get-ADObject -Server $PrincipalDom -Filter "samAccountName -eq '$PrincipalName'" -Properties objectSid} #Error if ($Principal -eq $null){ Write-Warning "Failed to bind to $($_.IdentityReference), quitting" Exit } $SID= new-object System.Security.Principal.SecurityIdentifier $Principal.ObjectSID $ObjectGUID = new-object Guid $_.ObjectType $ActiveDirectoryRights = $_.ActiveDirectoryRights $AccessControlType = $_.AccessControlType $InheritedObjectType = $_.InheritedObjectType $inheritedObjectGUID = new-object Guid $InheritedObjectType $InheritanceType = $_.InheritanceType [PSCustomObject]@{ DestinationOU = $OU SecurityPrincipal = $_.IdentityReference ObjectTypeName = $_.ObjectTypeName ActiveDirectoryRights = $_.ActiveDirectoryRights AccessControlType = $_.AccessControlType InheritedObjectTypeName = $_.InheritedObjectTypeName InheritanceType = $_.InheritanceType Activity = "Add ACE" Result = "Success" } # $ace = new-object System.DirectoryServices.ActiveDirectoryAccessRule $sid,$ActiveDirectoryRights,$AccessControlType,$ObjectGUID,$InheritanceType,$inheritedObjectGUID $acl.AddAccessRule($ace) $i++ } -End{ if ($btest) { $retval = 'Test mode only' }ELSE{ Try{ #Set-ACL fails outside of domain with server refused #set-acl -aclobject $acl -path RemoteAD:\$ou -WhatIf:$btest -ErrorAction Stop $de = [ADSI]"LDAP://$domain/$ou" $DE.psbase.ObjectSecurity.SetSecurityDescriptorSddlForm($acl.sddl) $DE.psbase.commitchanges() $retval = 'Success' }Catch{ write-warning "Error setting ACL!" $retval = 'Failed' + $Error[0].Exception.message } } [PSCustomObject]@{ DestinationOU = $OU SecurityPrincipal = $_.IdentityReference ObjectTypeName = '' ActiveDirectoryRights = '' AccessControlType = '' InheritedObjectTypeName = '' InheritanceType = '' Activity = "Set ACL" Result = $retval } } } $data | Export-Csv $logfile -NoTypeInformation Write-Progress -Activity "Script" -Status "Done" -Completed $msg = "Log file is:`n$logfile" $retval = [Microsoft.VisualBasic.Interaction]::MsgBox($msg,'OkOnly,defaultbutton1,Information', "Done")