diff --git a/Public/Invoke-GPOZaurr.ps1 b/Public/Invoke-GPOZaurr.ps1 index cf1fadc..b8abd54 100644 --- a/Public/Invoke-GPOZaurr.ps1 +++ b/Public/Invoke-GPOZaurr.ps1 @@ -12,9 +12,6 @@ [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Local')] [string[]] $Type, - [Parameter(ParameterSetName = 'Default')] - [Parameter(ParameterSetName = 'Local')] - [switch] $NoTranslation, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Local')] @@ -24,9 +21,11 @@ [Parameter(ParameterSetName = 'Local')] [switch] $FullObjects, + <# [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Local')] [ValidateSet('HTML', 'Object', 'Excel')][string[]] $OutputType = 'Object', + #> [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Local')] @@ -81,10 +80,13 @@ } else { [Array] $GPOs = Get-GPOZaurrAD -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } + # This caches single reports. + $TemporaryCachedSingleReports = [ordered] @{} + $TemporaryCachedSingleReports['ReportsSingle'] = [ordered] @{} + # This will be returned $Output = [ordered] @{} - $OutputByGPO = [ordered] @{} - $TranslatedOutput = [ordered] @{} - $CachedCategories = [ordered] @{} + $Output['Reports'] = [ordered] @{} + $Output['CategoriesFull'] = [ordered] @{} [Array] $GPOCategories = foreach ($GPO in $GPOs) { if ($GPOPath) { @@ -92,58 +94,92 @@ } else { [xml] $GPOOutput = Get-GPOReport -Guid $GPO.GUID -Domain $GPO.DomainName -ReportType Xml } - Get-GPOCategories -GPO $GPO -GPOOutput $GPOOutput.GPO -Splitter $Splitter -FullObjects:$FullObjects -CachedCategories $CachedCategories + Get-GPOCategories -GPO $GPO -GPOOutput $GPOOutput.GPO -Splitter $Splitter -FullObjects:$FullObjects -CachedCategories $Output['CategoriesFull'] } - - # Return Categories or save it + $Output['Categories'] = $GPOCategories | Select-Object -Property * -ExcludeProperty DataSet if ($CategoriesOnly) { - return $GPOCategories | Select-Object -Property * -ExcludeProperty DataSet - } else { - $Output['Categories'] = $GPOCategories | Select-Object -Property * -ExcludeProperty DataSet + # Return Categories only + return $Output['Categories'] } - # Save Cached Categories - $Output['CategoriesFull'] = $CachedCategories - # Process Reporting - $Output['Reports'] = [ordered] @{} - - if ($CachedCategories.Count -gt 0) { + # We check our dictionary for reports that are based on reports to make sure we run CodeSingle separatly + [Array] $FindRequiredSingle = foreach ($Key in $Script:GPODitionary.Keys) { + $Script:GPODitionary[$Key].ByReports.Report + } + # Build reports based on categories + if ($Output['CategoriesFull'].Count -gt 0) { foreach ($Report in $Type) { - foreach ($CategoryType in $Script:GPODitionary[$Report].Types) { - $Category = $CategoryType.Category $Settings = $CategoryType.Settings - # Those are checks for making sure we have data to be even able to process it - if (-not $CachedCategories[$Category]) { + if (-not $Output['CategoriesFull'][$Category]) { continue } - if (-not $CachedCategories[$Category][$Settings]) { + if (-not $Output['CategoriesFull'][$Category][$Settings]) { continue } # Translation - $CategorizedGPO = $CachedCategories[$Category][$Settings] + $CategorizedGPO = $Output['CategoriesFull'][$Category][$Settings] foreach ($GPO in $CategorizedGPO) { if (-not $Output['Reports'][$Report]) { $Output['Reports'][$Report] = [System.Collections.Generic.List[PSCustomObject]]::new() } + # Create temporary storage for "single gpo" reports + # it's required if we want to base reports on other reports later on + if (-not $TemporaryCachedSingleReports['ReportsSingle'][$Report]) { + $TemporaryCachedSingleReports['ReportsSingle'][$Report] = [System.Collections.Generic.List[PSCustomObject]]::new() + } + # Make sure translated gpo is null $TranslatedGpo = $null - if ($SingleObject) { + if ($SingleObject -or ($Report -in $FindRequiredSingle)) { + # We either create 1 GPO with multiple settings to return it as user requested it + # Or we process it only because we need to base it for reports based on other reports if ($Script:GPODitionary[$Report]['CodeSingle']) { $TranslatedGpo = Invoke-Command -ScriptBlock $Script:GPODitionary[$Report]['CodeSingle'] - } - } else { - if ($Script:GPODitionary[$Report]['Code']) { - $TranslatedGpo = Invoke-Command -ScriptBlock $Script:GPODitionary[$Report]['Code'] + if ($Report -in $FindRequiredSingle) { + foreach ($T in $TranslatedGpo) { + $TemporaryCachedSingleReports['ReportsSingle'][$Report].Add($T) + } + } + if ($SingleObject) { + foreach ($T in $TranslatedGpo) { + $Output['Reports'][$Report].Add($T) + } + } } } - foreach ($T in $TranslatedGpo) { - $Output['Reports'][$Report].Add($T) + if (-not $SingleObject) { + # We want each GPO to be listed multiple times if it makes sense for reporting + # think drive mapping - showing 1 mapping of a drive per object even if there are 50 drive mappings within 1 gpo + # this would result in 50 objects created + if ($Script:GPODitionary[$Report]['Code']) { + $TranslatedGpo = Invoke-Command -ScriptBlock $Script:GPODitionary[$Report]['Code'] + foreach ($T in $TranslatedGpo) { + $Output['Reports'][$Report].Add($T) + } + } } } } } } + # Those reports are based on other reports (for example already processed registry settings) + # This is useful where going thru registry collections may not be efficient enough to try and read it directly again + foreach ($Report in $Type) { + foreach ($ReportType in $Script:GPODitionary[$Report].ByReports) { + if (-not $Output['Reports'][$Report]) { + $Output['Reports'][$Report] = [System.Collections.Generic.List[PSCustomObject]]::new() + } + $FindReport = $ReportType.Report + foreach ($GPO in $TemporaryCachedSingleReports['ReportsSingle'][$FindReport]) { + $TranslatedGpo = Invoke-Command -ScriptBlock $Script:GPODitionary[$Report]['CodeReport'] + foreach ($T in $TranslatedGpo) { + $Output['Reports'][$Report].Add($T) + } + } + } + } + #Remove-EmptyValue -Hashtable $Output -Recursive return $Output <# foreach ($GPO in $GPOs) {