diff --git a/Examples/Example-22-RemoveLegacyADMFiles.ps1 b/Examples/Example-22-RemoveLegacyADMFiles.ps1 index 15c7518..e2108ff 100644 --- a/Examples/Example-22-RemoveLegacyADMFiles.ps1 +++ b/Examples/Example-22-RemoveLegacyADMFiles.ps1 @@ -1,3 +1,3 @@ Import-Module "$PSScriptRoot\..\GPoZaurr.psd1" -Force -Remove-GPOZaurrLegacyFiles -Verbose -BackupPath $Env:USERPROFILE\Desktop\BackupADM -BackupDated -RemoveEmptyFolders #-WhatIf #-LimitProcessing 2 -WhatIf \ No newline at end of file +Remove-GPOZaurrLegacyFiles -Verbose -BackupPath $Env:USERPROFILE\Desktop\BackupADM1 -BackupDated -RemoveEmptyFolders #-WhatIf #-LimitProcessing 2 -WhatIf \ No newline at end of file diff --git a/Examples/Example-31-FindAndRemoveEmptyFolders.ps1 b/Examples/Example-31-FindAndRemoveEmptyFolders.ps1 index b963189..fbe9a70 100644 --- a/Examples/Example-31-FindAndRemoveEmptyFolders.ps1 +++ b/Examples/Example-31-FindAndRemoveEmptyFolders.ps1 @@ -1,5 +1,7 @@ Import-Module "$PSScriptRoot\..\GPoZaurr.psd1" -Force -Get-GPOZaurrEmptyFolders | Where-Object { $_.Name -eq 'Adm' } | Format-Table * +# Report on empty folders +Get-GPOZaurrFolders -FolderType Empty | Format-Table * -Remove-GPOZaurrEmptyFolders -Folders Adm -Verbose -LimitProcessing 1 \ No newline at end of file +# ! NOT READY FOR EMPTY +#Remove-GPOZaurrFolders -FolderType Empty -Verbose -BackupPath $Env:USERPROFILE\Desktop\SomeBackup1 -WhatIf \ No newline at end of file diff --git a/Examples/Example-32-FindAndRemoveNTFRSFolders.ps1 b/Examples/Example-32-FindAndRemoveNTFRSFolders.ps1 new file mode 100644 index 0000000..bc61f2d --- /dev/null +++ b/Examples/Example-32-FindAndRemoveNTFRSFolders.ps1 @@ -0,0 +1,7 @@ +Import-Module "$PSScriptRoot\..\GPoZaurr.psd1" -Force + +# Report on NTFRS folders +Get-GPOZaurrFolders -FolderType NTFRS | Format-Table * + +# Remove NTFRS (broken replication folders) +Remove-GPOZaurrFolders -FolderType NTFRS -Verbose -BackupPath $Env:USERPROFILE\Desktop\SomeBackup -WhatIf \ No newline at end of file diff --git a/GPOZaurr.psd1 b/GPOZaurr.psd1 index 4723f45..5f8b4ff 100644 --- a/GPOZaurr.psd1 +++ b/GPOZaurr.psd1 @@ -5,9 +5,9 @@ CompatiblePSEditions = 'Desktop' Copyright = '(c) 2011 - 2020 Przemyslaw Klys @ Evotec. All rights reserved.' Description = 'Group Policy Eater is a PowerShell module that aims to gather information about Group Policies but also allows fixing issues that you may find in them.' - FunctionsToExport = 'Add-GPOPermission', 'Add-GPOZaurrPermission', 'Backup-GPOZaurr', 'Get-GPOZaurr', 'Get-GPOZaurrAD', 'Get-GPOZaurrBackupInformation', 'Get-GPOZaurrEmptyFolders', 'Get-GPOZaurrFiles', 'Get-GPOZaurrFilesPolicyDefinitions', 'Get-GPOZaurrLegacyFiles', 'Get-GPOZaurrLink', 'Get-GPOZaurrLinkSummary', 'Get-GPOZaurrOwner', 'Get-GPOZaurrPassword', 'Get-GPOZaurrPermission', 'Get-GPOZaurrPermissionConsistency', 'Get-GPOZaurrSysvol', 'Get-WMIFilter', 'Get-GPOZaurrWMI', 'Invoke-GPOZaurr', 'Invoke-GPOZaurrPermission', 'New-GPOZaurrWMI', 'Remove-GPOPermission', 'Remove-GPOZaurr', 'Remove-GPOZaurrEmptyFolders', 'Remove-GPOZaurrLegacyFiles', 'Remove-GPOZaurrOrphanedSysvolFolders', 'Remove-GPOZaurrPermission', 'Remove-GPOZaurrWMI', 'Repair-GPOZaurrPermissionConsistency', 'Restore-GPOZaurr', 'Save-GPOZaurrFiles', 'Select-GPOTranslation', 'Set-GPOOwner', 'Set-GPOZaurrOwner' + FunctionsToExport = 'Add-GPOPermission', 'Add-GPOZaurrPermission', 'Backup-GPOZaurr', 'Get-GPOZaurr', 'Get-GPOZaurrAD', 'Get-GPOZaurrBackupInformation', 'Get-GPOZaurrFiles', 'Get-GPOZaurrFilesPolicyDefinitions', 'Get-GPOZaurrFolders', 'Get-GPOZaurrLegacyFiles', 'Get-GPOZaurrLink', 'Get-GPOZaurrLinkSummary', 'Get-GPOZaurrOwner', 'Get-GPOZaurrPassword', 'Get-GPOZaurrPermission', 'Get-GPOZaurrPermissionConsistency', 'Get-GPOZaurrSysvol', 'Get-WMIFilter', 'Get-GPOZaurrWMI', 'Invoke-GPOZaurr', 'Invoke-GPOZaurrPermission', 'New-GPOZaurrWMI', 'Remove-GPOPermission', 'Remove-GPOZaurr', 'Remove-GPOZaurrFolders', 'Remove-GPOZaurrLegacyFiles', 'Remove-GPOZaurrOrphanedSysvolFolders', 'Remove-GPOZaurrPermission', 'Remove-GPOZaurrWMI', 'Repair-GPOZaurrPermissionConsistency', 'Restore-GPOZaurr', 'Save-GPOZaurrFiles', 'Select-GPOTranslation', 'Set-GPOOwner', 'Set-GPOZaurrOwner' GUID = 'f7d4c9e4-0298-4f51-ad77-e8e3febebbde' - ModuleVersion = '0.0.49' + ModuleVersion = '0.0.50' PowerShellVersion = '5.1' PrivateData = @{ PSData = @{ diff --git a/Public/Get-GPOZaurrEmptyFolders.ps1 b/Public/Get-GPOZaurrEmptyFolders.ps1 deleted file mode 100644 index 1a9ea3e..0000000 --- a/Public/Get-GPOZaurrEmptyFolders.ps1 +++ /dev/null @@ -1,29 +0,0 @@ -function Get-GPOZaurrEmptyFolders { - [cmdletBinding()] - param( - [alias('ForestName')][string] $Forest, - [string[]] $ExcludeDomains, - [alias('Domain', 'Domains')][string[]] $IncludeDomains, - [System.Collections.IDictionary] $ExtendedForestInformation, - [switch] $AllFolders - ) - $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation - foreach ($Domain in $ForestInformation.Domains) { - Get-ChildItem -Path "\\$Domain\SYSVOL\$Domain\policies" -ErrorAction SilentlyContinue -Recurse -ErrorVariable err -Force -Directory | ForEach-Object { - $FullFolder = Test-Path -Path "$($_.FullName)\*" - if (-not $FullFolder -or $AllFolders) { - [PSCustomObject] @{ - FullName = $_.FullName - IsEmptyFolder = -not $FullFolder - Name = $_.Name - Root = $_.Root - Parent = $_.Parent - CreationTime = $_.CreationTime - LastWriteTime = $_.LastWriteTime - Attributes = $_.Attributes - DomainName = $Domain - } - } - } - } -} \ No newline at end of file diff --git a/Public/Get-GPOZaurrFiles.ps1 b/Public/Get-GPOZaurrFiles.ps1 index 4001a8f..54ec864 100644 --- a/Public/Get-GPOZaurrFiles.ps1 +++ b/Public/Get-GPOZaurrFiles.ps1 @@ -50,6 +50,9 @@ "\\$Domain\NETLOGON" = @{ Name = 'NETLOGON Scripts' } + "\\$Domain\SYSVOL\$Domain\DfsrPrivate" = @{ + Name = 'DfsrPrivate' + } "\\$Domain\SYSVOL\$Domain" = @{ Name = 'SYSVOL Root' } diff --git a/Public/Get-GPOZaurrFolders.ps1 b/Public/Get-GPOZaurrFolders.ps1 new file mode 100644 index 0000000..905f016 --- /dev/null +++ b/Public/Get-GPOZaurrFolders.ps1 @@ -0,0 +1,140 @@ +function Get-GPOZaurrFolders { + [cmdletBinding()] + param( + [ValidateSet('All', 'Netlogon', 'Sysvol')][string[]] $Type = 'All', + [ValidateSet('All', 'NTFRS', 'Empty')][string] $FolderType = 'All', + [alias('ForestName')][string] $Forest, + [string[]] $ExcludeDomains, + [alias('Domain', 'Domains')][string[]] $IncludeDomains, + [System.Collections.IDictionary] $ExtendedForestInformation, + [switch] $AsHashTable + ) + $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation + foreach ($Domain in $ForestInformation.Domains) { + $Path = @( + if ($Type -contains 'All') { + "\\$Domain\SYSVOL\$Domain" + } + if ($Type -contains 'Sysvol') { + "\\$Domain\SYSVOL\$Domain\policies" + } + if ($Type -contains 'NetLogon') { + "\\$Domain\NETLOGON" + } + ) + # Order does matter + $Folders = [ordered] @{ + "\\$Domain\SYSVOL\$Domain\policies\PolicyDefinitions" = @{ + Name = 'SYSVOL PolicyDefinitions' + } + "\\$Domain\SYSVOL\$Domain\policies" = @{ + Name = 'SYSVOL Policies' + } + "\\$Domain\SYSVOL\$Domain\scripts" = @{ + Name = 'NETLOGON Scripts' + } + "\\$Domain\SYSVOL\$Domain\StarterGPOs" = @{ + Name = 'SYSVOL GPO Starters' + } + "\\$Domain\NETLOGON" = @{ + Name = 'NETLOGON Scripts' + } + "\\$Domain\SYSVOL\$Domain\DfsrPrivate" = @{ + Name = 'DfsrPrivate' + } + "\\$Domain\SYSVOL\$Domain" = @{ + Name = 'SYSVOL Root' + } + } + $Exclusions = @{ + DfsrPrivate = @{ + ConflictAndDeleted = $true + Deleted = $true + Installing = $true + } + 'SYSVOL Policies' = @{ + User = $true + Machine = $true + } + 'NETLOGON Scripts' = @{ + + } + 'SYSVOL Root' = @{ + + } + 'SYSVOL GPO Starters' = @{ + + } + 'SYSVOL PolicyDefinitions' = @{ + + } + } + + Get-ChildItem -Path $Path -ErrorAction SilentlyContinue -Recurse -ErrorVariable +err -Force -Directory | ForEach-Object { + $FileType = foreach ($Key in $Folders.Keys) { + if ($_.FullName -like "$Key*") { + $Folders[$Key] + break + } + } + $RootFolder = $Folders["$($_.FullName)"] + if ($RootFolder) { + $IsRootFolder = $true + } else { + $IsRootFolder = $false + } + + $IsExcluded = $Exclusions["$($FileType.Name)"]["$($_.Name)"] -is [bool] + if ($IsRootFolder -and $IsExcluded -eq $false) { + $IsExcluded = $true + } + + $FullFolder = Test-Path -Path "$($_.FullName)\*" + $BrokenReplicationRoot = $_.Name -like '*_NTFRS_*' + $BrokenReplicationChild = $_.FullName -like '*_NTFRS_*' -and $_.Name -notlike '*_NTFRS_*' + $BrokenReplication = $_.FullName -like '*_NTFRS_*' + + $Object = [ordered] @{ + FolderType = $FileType.Name + FullName = $_.FullName + IsEmptyFolder = -not $FullFolder + IsBrokenReplication = $BrokenReplication + IsBrokenReplicationRoot = $BrokenReplicationRoot + IsBrokenReplicationChild = $BrokenReplicationChild + IsRootFolder = $IsRootFolder + IsExcluded = $IsExcluded + Name = $_.Name + Root = $_.Root + Parent = $_.Parent + CreationTime = $_.CreationTime + LastWriteTime = $_.LastWriteTime + Attributes = $_.Attributes + DomainName = $Domain + } + if (-not $Object.IsExcluded) { + if ($FolderType -eq 'Empty' -and $Object.IsEmptyFolder -eq $true) { + if ($AsHashTable) { + $Object + } else { + [PSCustomObject] $Object + } + } elseif ($FolderType -eq 'NTFRS' -and $Object.IsBrokenReplicationRoot -eq $true) { + if ($AsHashTable) { + $Object + } else { + [PSCustomObject] $Object + } + } elseif ($FolderType -eq 'All') { + if ($AsHashTable) { + $Object + } else { + [PSCustomObject] $Object + } + } + } + } + } + foreach ($e in $err) { + Write-Warning "Get-GPOZaurrFolders - $($e.Exception.Message) ($($e.CategoryInfo.Reason))" + } +} \ No newline at end of file diff --git a/Public/Remove-GPOZaurrEmptyFolders.ps1 b/Public/Remove-GPOZaurrEmptyFolders.ps1 deleted file mode 100644 index be8b081..0000000 --- a/Public/Remove-GPOZaurrEmptyFolders.ps1 +++ /dev/null @@ -1,25 +0,0 @@ -function Remove-GPOZaurrEmptyFolders { - [cmdletBinding(SupportsShouldProcess)] - param( - [ValidateSet('Adm')][string] $Folders, - [int] $LimitProcessing = [int32]::MaxValue, - [alias('ForestName')][string] $Forest, - [string[]] $ExcludeDomains, - [alias('Domain', 'Domains')][string[]] $IncludeDomains, - [System.Collections.IDictionary] $ExtendedForestInformation - ) - Get-GPOZaurrEmptyFolders -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation | Where-Object { - foreach ($Folder in $Folders) { - if ($_.IsEmptyFolder -eq $true -and $_.Name -eq $Folder) { - $_ - } - } - } | Select-Object | Select-Object -First $LimitProcessing | ForEach-Object { - try { - Write-Verbose "Remove-GPOZaurrEmptyFolders - Removing $($_.FullName)" - Remove-Item -Path $_.FullName - } catch { - Write-Warning "Remove-GPOZaurrEmptyFolders - Failed to remove directory $($_.FullName): $($_.Exception.Message)." - } - } -} \ No newline at end of file diff --git a/Public/Remove-GPOZaurrFolders.ps1 b/Public/Remove-GPOZaurrFolders.ps1 new file mode 100644 index 0000000..ebd67f8 --- /dev/null +++ b/Public/Remove-GPOZaurrFolders.ps1 @@ -0,0 +1,62 @@ +function Remove-GPOZaurrFolders { + [cmdletBinding(SupportsShouldProcess)] + param( + [string] $BackupPath, + [switch] $BackupDated, + [ValidateSet('All', 'Netlogon', 'Sysvol')][string[]] $Type = 'All', + [Parameter(Mandatory)][ValidateSet('NTFRS', 'Empty')][string] $FolderType, + [string[]] $FolderName, + [int] $LimitProcessing = [int32]::MaxValue, + [alias('ForestName')][string] $Forest, + [string[]] $ExcludeDomains, + [alias('Domain', 'Domains')][string[]] $IncludeDomains, + [System.Collections.IDictionary] $ExtendedForestInformation + ) + if ($BackupPath) { + if ($BackupDated) { + $BackupFinalPath = "$BackupPath\$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss'))" + } else { + $BackupFinalPath = $BackupPath + } + } else { + $BackupFinalPath = '' + } + + Get-GPOZaurrFolders -Type $Type -FolderType $FolderType -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation | Where-Object { + if ($FolderName) { + foreach ($Folder in $FolderName) { + if ($_.Name -eq $Folder) { + $_ + } + } + } else { + $_ + } + } | Select-Object | Select-Object -First $LimitProcessing | ForEach-Object { + if ($BackupFinalPath) { + $SYSVOLRoot = "\\$($_.DomainName)\SYSVOL\$($_.DomainName)\" + $DestinationFile = ($_.FullName).Replace($SYSVOLRoot, '') + #$DestinationMissingFolder = $DestinationFile.Replace($DestinationFile, '') + $DestinationFilePath = [system.io.path]::Combine($BackupFinalPath, $DestinationFile) + #$DestinationFolderPath = [system.io.path]::Combine($BackupFinalPath, $DestinationMissingFolder) + + Write-Verbose "Remove-GPOZaurrFolders - Backing up $($_.FullName)" + Try { + Copy-Item -LiteralPath $_.FullName -Recurse -Destination $DestinationFilePath -ErrorAction Stop -Force + $BackupWorked = $true + } catch { + Write-Warning "Remove-GPOZaurrFolders - Error backing up error: $($_.Exception.Message)" + $BackupWorked = $false + } + + } + if ($BackupWorked -or $BackupFinalPath -eq '') { + try { + Write-Verbose "Remove-GPOZaurrFolders - Removing $($_.FullName)" + Remove-Item -Path $_.FullName -Force -Recurse + } catch { + Write-Warning "Remove-GPOZaurrFolders - Failed to remove directory $($_.FullName): $($_.Exception.Message)." + } + } + } +} \ No newline at end of file