<# .Synopsis Export a list of GPOs with logon/logoff/startup/shutdown scripts, .DESCRIPTION Export a list of GPOs for a selected domain with logon/logoff/startup/shutdown scripts to a CSV file, including the script names and parameters .PARAMETER .EXAMPLE Runs interatively .Notes Alan Kaplan version 1 10-3-2017 version 1.1 10-4-2017. Improved error handling, added indicator of files in SysVol, removed dependency on GPO Module version 2.0 10-5-2017. Major changes. Added search for Powershell scripts, GPOType indicator, folder location for scripts, export file unicode version 3 10-12-2017. Again rewritten, to search file names as a workflow,improved error handling. #> #Requires -version 3 $ScriptTypes = "Logon,Logoff,Startup,Shutdown".Split(",") $varList = "LogonCmdLine,LogonParams,LogoffCmdLine,LogoffParams,SysVolFiles,StartupCmdLine,StartupParams,ShutdownCmdLine,ShutdownParams,Remarks".Split(",") Function Copy-Array ($OriginalArray){ #https://stackoverflow.com/questions/29699026/powershell-copy-an-array-completely # Serialize and Deserialize data using PSSerializer: $_TempCliXMLString = [System.Management.Automation.PSSerializer]::Serialize($OriginalArray, [int32]::MaxValue) [array][System.Management.Automation.PSSerializer]::Deserialize($_TempCliXMLString) } Function Get-GPODisplayName($domain,$Guid){ #Easier to later add brackets in filter $GUID = [regex]::Replace($GUID,"{|}",'') $SearchRoot = "LDAP://$domain" $filter = "(&(objectCategory=groupPolicyContainer)(name={$GUID})) " $ADSearcher = [adsisearcher] '' $ADSearcher.SearchRoot = $SearchRoot $ADSearcher.SearchScope = "SubTree" $ADSearcher.CacheResults = $false $adSearcher.ReferralChasing = "All" $ADSearcher.Filter = $filter $retval = $ADSearcher.FindOne() if ($retval) { $retval.Properties.displayname[0] } ELSE { 'Unable to Determine' } } Function Search-Scripts ($files,$GUIDTxt){ Foreach ($path in $files) { Write-Progress "Reading $path" $Guid = [regex]::Matches($path,'\{([^}]*)\}').value $GPOName = get-GPODisplayName $domain $Guid $IniData = get-inifile $path #If file is not empty if ($IniData.Keys.Count -gt 0) { Foreach ($ScriptType in $ScriptTypes){ if ($IniData.Keys.Count -gt 0){ $iStart = 0 if (($IniData["$ScriptType"].Keys).length -eq 0){ $iEnd = -1 }ELSE{ $iEnd = (($IniData["$ScriptType"].Keys | %{[int]$_.substring(0,1)}) | measure -Maximum).Maximum } for ($i = $iStart; $i -le $iEnd; $i++) { $varList = "LogonCmdLine,LogonParams,LogoffCmdLine,LogoffParams,SysVolFiles,StartupCmdLine,StartupParams,ShutdownCmdLine,ShutdownParams,Remarks".Split(",") $varList | Foreach {Set-Variable $_ -value '' } $inSysVol = $false $ScriptTypes = "Logon,Logoff,Startup,Shutdown".Split(",") Foreach ($ScriptType in $ScriptTypes){ if($IniData["$ScriptType"].length -gt 0) { $cmdVal = $IniData["$ScriptType"]["$i`CmdLine"] if ($cmdVal.length -gt 0 ) { $cmdVal = $cmdVal.Replace('"','') } Else { $cmdVal = '' } Set-Variable -name "$ScriptType`CmdLine" -value $cmdVal $paramVal = $IniData["$ScriptType"]["$i`Parameters"] if ($paramVal.length -gt 0) { $paramVal = $paramVal.Replace('"','').Replace('-',[char]0x2013) } ELSE { $paramVal = '' } Set-variable -name "$ScriptType`Params" -value $ParamVal #identify odd command parameter combinations if ((Get-Variable "$ScriptType`CmdLine" -value).Contains('.') -eq $false) { Set-Variable "$ScriptType`CmdLine" -value $paramVal Set-variable -name "$ScriptType`Params" -value '' $Remarks = "Suspect CmdLine moved" } } } if($IniData['Error'].length -gt 0) { $Remarks = $IniData['Error'] $GPOName = 'Unable to determine' } $scriptPath = '' [System.Collections.ArrayList]$tarray = "$LogonCmdLine,$LogoffCmdLine,$StartUpCmdLine,$ShutdownCmdLine".Split(",") foreach ($item in $($tArray)) {if ($item.length -lt 2 ){$tarray.Remove($item)}} if ($tArray){ $tarray2 = Copy-Array $tarray [array]$inSysVol = $tArray | foreach { ( ((split-path $_ -Leaf) -eq $_) ` -or ($_ -match 'netlogon') ` -or ($_ -match 'SysVol') ) } $SysVolFiles = $inSysVol.Contains($true) if ($SysVolFiles) { [array]$inGPOFolder = $tArray2 | foreach { ((split-path $_ -Leaf) -eq $_) } } [string]$scriptpath = ,$LogonCmdLine,$LogoffCmdLine,$StartUpCmdLine,$ShutdownCmdLine| Where {$_} | Split-Path -Parent if ($SysVolFiles -and ($scriptpath.StartsWith('\\') -eq $false)){ $ScriptFolder = Split-Path $path -parent #[regex]::Replace($path,'\\(?:.(?!\\))+$','') $scriptPath = $ScriptFolder } $GPOType = if ($path -match 'machine'){'Machine'}ELSE{'User'} [PSCustomObject]@{ Domain = $domain GPOName = $GPOName Type = $GPOType FilesInSysVol = $SysVolFiles LogonCmdLine =$LogonCmdLine LogonParams = $LogonParams LogoffCmdLine =$LogoffCmdLine LogoffParams = $LogoffParams StartUpCmdLine =$StartUpCmdLine StartUpParams = $StartUpParams ShutdownCmdLine =$ShutdownCmdLine ShutdownParams = $ShutdownParams GPOGUID = $Guid ScriptFolder = $ScriptPath Remarks = $Remarks Errors = '' } } } } } } } } #https://www.simple-talk.com/sysadmin/powershell/powershell-data-basics-file-based-data/ Function Get-IniFile ([string]$fileName) { $ini = @{} switch -regex -file $fileName { "^\[(.+)\]$" { # recognize a section $section = $matches[1] $ini[$section] = @{} } "^\s*([^#]+?)\s*=\s*(.*)" { # recognize a property $name,$value = $matches[1..2] if (!(Test-path variable:\section)) { $section = "-unknown-" $ini[$section] = @{} } $ini[$section][$name] = $value.trim() } } $ini } Function Process-Errors() { foreach ($path in $accessErrors) { $Guid = [regex]::Matches($path,'\{([^}]*)\}').value $GPOName = get-GPODisplayName $domain $Guid [PSCustomObject]@{ Domain = $domain GPOName = $GPOName Type = '' FilesInSysVol = '' LogonCmdLine = '' LogonParams = '' LogoffCmdLine ='' LogoffParams = '' StartUpCmdLine ='' StartUpParams = '' ShutdownCmdLine ='' ShutdownParams = '' GPOGUID = $Guid ScriptFolder = '' Remarks = '' Errors = "Error Accessing $path" } } } Function Get-FolderNames($domain){ gci "\\$domain\sysvol\$domain\Policies" -Directory -Depth 0 | where {$_.Name -ne "PolicyDefinitions"} | select -ExpandProperty fullname } WorkFlow Run-wfGetIniFiles { param([string[]]$folders) ForEach -parallel ($searchpath in $folders){ InLineScript { pushd $using:searchPath $AccessErrors = @() $params = @{ Erroraction = 'SilentlyContinue' Recurse = $true Force = $true Filter = '*scripts.ini' ErrorVariable = "+AccessErrors" OutVariable = 'INIList' File = $true } Get-ChildItem @params| Out-Null Popd $iniList $AccessErrors }#End Inline } #End Foreach Parallel }#End WorkFlow Add-Type -assemblyname Microsoft.visualBasic $domain = [Microsoft.VisualBasic.Interaction]::InputBox("Enter domain to search.",` "Domain", $env:USERDNSDOMAIN) if ($domain.Length -eq 0){Exit} #Prompt for save file $dTxt = $domain.Replace(".","_") $LogFile = "$env:userprofile\desktop\$dTxt GPO Scripts.csv" $LogFile = [Microsoft.VisualBasic.Interaction]::InputBox("Enter the path for the csv log file.", "Log File", $LogFile) if ($Logfile.Length -eq 0){Exit} $folders = Get-FolderNames $domain Write-progress "Getting list of `*script.ini files from $domain" -Status "This can take a long time, please wait ..." $data = Run-wfGetIniFiles $folders $accessErrors = ($data).Errorcategory_targetname | Where {$_.length -gt 0} $iniList = $data.fullname | where {$_.length -gt 0} $retval = search-scripts $INIList if ($AccessErrors.count -ne 0){ $retval += Process-Errors } $retval| Sort GPO |Export-Csv $LogFile -NoTypeInformation -Encoding Unicode Write "Done. Unicode log file is $Logfile"