├── .github └── FUNDING.yml ├── Compare files in folders.ps1 ├── DNS Impostor Checker.ps1 ├── Digital Clock.ps1 ├── Find JSON attribute by name.ps1 ├── Find file type.ps1 ├── Get chunk at offset.ps1 ├── IP range from cidr.ps1 ├── Leaky.ps1 ├── Log selector.ps1 ├── Parse smbstatus.ps1 ├── Pass json as parameters.ps1 ├── Query SQLite database.ps1 ├── README.md ├── Regrecent.ps1 ├── Send to Viewer.ps1 ├── Sendto Checksummer.ps1 ├── Sendto folder size.ps1 ├── Set photo dates.ps1 ├── Shortcuts to csv.ps1 ├── Twitter Statistics.ps1 ├── Update dynamic dns.ps1 ├── Zombie Handle Generator.ps1 └── helloworld.exe /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: guyrleech 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: guyrleech 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /Compare files in folders.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 4 2 | <# 3 | Compare files by version, date, checksum and size between two folders. Also show files in one or other folder but not in both. 4 | 5 | If using a gridview for output, text files selected in that will then be differenced into separate grid views 6 | 7 | @guyrleech 2019 8 | 9 | Modification History: 10 | 11 | 15/02/19 GRL Added size difference column 12 | #> 13 | 14 | <# 15 | .SYNOPSIS 16 | 17 | Compare files in folders in two different folders. Compares first by file attributes and then if they are the same by checksum. Also shows folder in one folder or other but not both. 18 | Text files selected in the grid view will then be differenced into separate grid views to show the actual differences between the two files. 19 | 20 | .PARAMETER folder1 21 | 22 | A folder containing one or more files. 23 | 24 | .PARAMETER folder2 25 | 26 | A folder containing one or more files. 27 | 28 | .PARAMETER recurse 29 | 30 | Recurse the folder structures. The default is to only do the top level. 31 | 32 | .PARAMETER include 33 | 34 | A comma separated list of regular expressions where only files that match one of these will be processed. 35 | 36 | .PARAMETER exclude 37 | 38 | A comma separated list of regular expressions where a file that matches any of these will not be processed. 39 | 40 | .PARAMETER differentOnly 41 | 42 | Only show files which are different or do not exist on ond of the folders 43 | 44 | .PARAMETER date 45 | 46 | Compare date stamps on files 47 | 48 | .PARAMETER nogridview 49 | 50 | Do not display the results in a grid view, write them to the standard output 51 | 52 | .PARAMETER skipBlank 53 | 54 | When showing the differences between two files, don't show blank lines 55 | 56 | .PARAMETER compareBinary 57 | 58 | Compare binary file types. The default does not. This may not work as expected. 59 | 60 | .PARAMETER binaryTypes 61 | 62 | A comma separated list of extensions that signify the file is has binary content and should not be compared unless -compareBinary is specified. 63 | 64 | .EXAMPLE 65 | 66 | & '.\Compare files in folders.ps1' -folder1 "C:\Temp\Office ADMX 4810.1000" -folder2 "C:\Temp\Office ADMX 4768.1000" -recurse -skipBlank -differentOnly 67 | 68 | Compare all files in the specified folders and subfolders, except for datestamps, display in a gridview and show the text differences in any files selected when the "OK" button is clicked except for blank lines. 69 | 70 | .EXAMPLE 71 | 72 | & '.\Compare files in folders.ps1' -folder1 "C:\Temp\Office ADMX 4810.1000" -folder2 "C:\Temp\Office ADMX 4768.1000" -recurse -include '.adml$' 73 | 74 | Compare all .adml files in the specified folders and subfolders, except for datestamps, display in a gridview and show the text differences in any files selected when the "OK" button is clicked. 75 | 76 | #> 77 | 78 | [CmdletBinding()] 79 | 80 | Param 81 | ( 82 | [Parameter(Mandatory=$true)] 83 | [string]$folder1 , 84 | [Parameter(Mandatory=$true)] 85 | [string]$folder2 , 86 | [string[]]$include , 87 | [string[]]$exclude , 88 | [switch]$recurse , 89 | [switch]$differentOnly , 90 | [switch]$date , 91 | [switch]$noGridView , 92 | [switch]$skipBlank , 93 | [switch]$compareBinary , 94 | [string[]]$binaryTypes = @( 'ocx' , 'exe' , 'iso' , 'vmdk' , 'vhd' , 'vhdx' , 'dll' , 'zip' , 'tar' , 'tz' , 'gz' , 'tgz' , '7z' , 'docx' , 'doc' , 'xls' , 'xlsx' , 'docm' , 'ppt' , 'pptx' , 'mdb' , 'ldb' , 'pdf' , 'bmp' , 'png' , 'jpg' , 'gif' , 'tif' , 'tiff' , 'jpeg' , 'dmp' ) 95 | ) 96 | 97 | [hashtable]$params = @{ 'File' = $true ; 'Force' = $true } 98 | 99 | if( $recurse ) 100 | { 101 | $params.Add( 'Recurse' ,$true ) 102 | } 103 | 104 | [int]$startPathLength = $folder1.Length 105 | [int]$totalFiles = 0 106 | [int]$excluded = 0 107 | 108 | [System.Collections.ArrayList]$results = @( Get-ChildItem $folder1 @params | ForEach-Object ` 109 | { 110 | $file1 = $_ 111 | [bool]$processIt = $false 112 | if( $include -and $include.Count ) 113 | { 114 | ForEach( $item in $include ) 115 | { 116 | if( $file1 -match $item ) 117 | { 118 | $processIt = $true 119 | break 120 | } 121 | } 122 | } 123 | else 124 | { 125 | $processIt = $true 126 | } 127 | if( $exclude -and $exclude.Count ) 128 | { 129 | [bool]$matched = $false 130 | ForEach( $item in $exclude ) 131 | { 132 | if( $file1 -match $item ) 133 | { 134 | $matched = $true 135 | break 136 | } 137 | } 138 | $processIt = ! $matched 139 | } 140 | if( $processIt ) 141 | { 142 | $totalFiles++ 143 | [bool]$sameVersion = $false 144 | [bool]$sameSize = $false 145 | [bool]$sameModificationTime = ! $date 146 | [bool]$sameChecksum = $false 147 | ## Get Path relative to our starting point so we can find other file 148 | [string]$file2path = Join-Path $folder2 ($file1.FullName).Substring( $startPathLength ) 149 | if( Test-Path $file2path -ErrorAction SilentlyContinue ) 150 | { 151 | $file2 = Get-ChildItem $file2path -ErrorAction SilentlyContinue 152 | } 153 | else 154 | { 155 | $file2 = $null 156 | $file2path = $null 157 | } 158 | Write-Verbose "`"$file1`" : `"$file2`"" 159 | if( $file2 ) 160 | { 161 | $sameVersion = (($file1.VersionInfo).FileVersion -eq ($file2.VersionInfo).FileVersion) 162 | $sameSize = ($file1.Length -eq $file2.Length) 163 | $sameModificationTime = if( ! $date ) { $true } else { ($file1.LastWriteTime -eq $file2.LastWriteTime) } 164 | ## Only calculate checksum if other file properties are all equal thus far otherwise must be different checksums 165 | if( $sameSize -and $sameVersion ) 166 | { 167 | $sameChecksum = ((Get-FileHash $file1.FullName).Hash -eq (Get-FileHash $file2.FullName).Hash) 168 | } 169 | } 170 | else 171 | { 172 | $file2 = $null 173 | } 174 | if( ! $differentOnly -or ! $sameVersion -or ! $sameModificationTime -or ! $sameSize -or ! $sameChecksum ) 175 | { 176 | $result = [pscustomobject][ordered]@{ 177 | 'File1' = $file1.FullName 178 | 'File2' = $file2path 179 | 'Type' = [System.IO.Path]::GetExtension( $file1.FullName ) -replace '^\.' , '' 180 | 'File1 Version' = ( $file1 | Select -ExpandProperty VersionInfo | Select -ExpandProperty FileVersion ) ##$(if( $file1.PSObject.properties[ 'VersionInfo' ] ) { $file1.VersionInfo | Select -ExpandProperty FileVersion }) 181 | 'File1 modification time' = $file1.LastWriteTime 182 | 'File1 size' = [int]$file1.Length 183 | 'Size Difference' = [int]($file1.Length - ($file2 | Select -ExpandProperty Length)) 184 | 'File2 Version' = ( $file2 | Select -ExpandProperty VersionInfo | Select -ExpandProperty FileVersion ) #$(if( $file2 -and $file2.PSObject.properties[ 'VersionInfo' ] ) { $file2.VersionInfo | Select -ExpandProperty FileVersion }) 185 | 'File2 size' = [int]( $file2 | Select -ExpandProperty Length) 186 | 'File2 modification time' = ($file2 | Select -ExpandProperty LastWriteTime) 187 | 'Same Version' = $sameVersion 188 | 'Same size' = $sameSize 189 | 'Same checksum' = $sameChecksum 190 | } 191 | if( $date ) 192 | { 193 | Add-Member -InputObject $result -MemberType NoteProperty -Name 'Same modification time' -value $sameModificationTime 194 | } 195 | $result 196 | } 197 | } 198 | }) 199 | 200 | ## also need to look for files in $folder2 not in $folder1 but don't need to compare properties if do exist since already done 201 | $startPathLength = $folder2.Length 202 | Get-ChildItem $folder2 @params | ForEach-Object ` 203 | { 204 | $file2 = $_ 205 | [bool]$processIt = $false 206 | if( $include -and $include.Count ) 207 | { 208 | ForEach( $item in $include ) 209 | { 210 | if( $file2 -match $item ) 211 | { 212 | $processIt = $true 213 | break 214 | } 215 | } 216 | } 217 | else 218 | { 219 | $processIt = $true 220 | } 221 | if( $exclude -and $exclude.Count ) 222 | { 223 | [bool]$matched = $false 224 | ForEach( $item in $exclude ) 225 | { 226 | if( $file2 -match $item ) 227 | { 228 | $matched = $true 229 | break 230 | } 231 | } 232 | $processIt = ! $matched 233 | } 234 | if( $processIt ) 235 | { 236 | ## Get Path relative to our starting point so we can find other file 237 | [string]$file1path = Join-Path $folder1 ($file2.FullName).Substring( $startPathLength ) 238 | if( ! ( Test-Path $file1path -ErrorAction SilentlyContinue ) ) 239 | { 240 | $totalFiles++ 241 | [void]$results.Add( 242 | ([pscustomobject][ordered]@{ 243 | 'File1' = $null 244 | 'File2' = $file2.FullName 245 | 'Type' = [System.IO.Path]::GetExtension( $file2.FullName ) -replace '^\.' , '' 246 | 'File1 Version' = $null 247 | 'File1 modification time' = $null 248 | 'File1 size' = $null 249 | 'File2 Version' = (($file2.VersionInfo).FileVersion) 250 | 'File2 size' = $file2.Length 251 | 'File2 modification time' = $file2.LastWriteTime 252 | 'Same Version' = $false 253 | 'Same size' = $false 254 | 'Same checksum' = $false 255 | })) 256 | } 257 | } 258 | } 259 | 260 | [string]$title = ( "Found {0} different files out of {1} between `"{2}`" and `"{3}`"" -f $results.Count , $totalFiles , $folder1 , $folder2 ) 261 | 262 | Write-Verbose $title 263 | 264 | if( ! $noGridView ) 265 | { 266 | [array]$selected = @( $results | Out-GridView -Title $title -PassThru ) 267 | if( $selected -and $selected.Count ) 268 | { 269 | ForEach( $differentFile in $selected ) 270 | { 271 | if( $differentFile.file1 -and $differentFile.file2 ) 272 | { 273 | if( ! $compareBinary -and $differentFile.Type -in $binaryTypes ) 274 | { 275 | Write-Warning "Skipping `"$($differentFile.file1)`" as its extension is for a binary file, not text" 276 | } 277 | else 278 | { 279 | ## Include equal lines so can track lines numbers 280 | [int]$linenumber = 1 281 | [int]$file1diffs = 0 282 | [int]$file2diffs = 0 283 | [array]$fileDifferences = @( Compare-Object -ReferenceObject (Get-Content -Path $differentFile.file1) -DifferenceObject (Get-Content -Path $differentFile.file2) -IncludeEqual | Sort-Object { $_.InputObject.ReadCount } | ForEach-Object ` 284 | { 285 | if( $_.SideIndicator -match '^=[=>]$') 286 | { 287 | $lineNumber = $_.InputObject.ReadCount 288 | } 289 | if( $_.SideIndicator -ne '==' ) 290 | { 291 | Write-Verbose ('{0}: {1} {2}' -f $lineNumber , $_.SideIndicator , $_.InputObject ) 292 | [string]$file1line = "" 293 | [string]$file2line = "" 294 | if( $_.SideIndicator -eq '<=' ) 295 | { 296 | $file1line = $_.InputObject 297 | $file1diffs++ 298 | } 299 | elseif( $_.SideIndicator -eq '=>' ) 300 | { 301 | $file2line = $_.InputObject 302 | $file2diffs++ 303 | } 304 | 305 | if( ! $skipBlank -or ! [string]::IsNullOrEmpty( $file1line ) -or ! [string]::IsNullOrEmpty( $file2line ) ) 306 | { 307 | [pscustomobject]@{ 308 | 'Around Line Number' = $lineNumber 309 | 'File1' = $file1line 310 | 'File2' = $file2line 311 | } 312 | } 313 | } 314 | }) 315 | if( $fileDifferences -and $fileDifferences.Count ) 316 | { 317 | [string]$title = if( $file1diffs -and $file2diffs ) 318 | { 319 | "$($fileDifferences.Count) differences" 320 | } 321 | elseif( $file1diffs ) 322 | { 323 | "$($fileDifferences.Count) lines only in first file" 324 | } 325 | else 326 | { 327 | "$($fileDifferences.Count) lines only in second file" 328 | } 329 | $fileDifferences | Select -Property 'Around Line Number',@{n="$($differentFile.file1)";e={$_.File1}},@{n="$($differentFile.file2)";e={$_.File2}} | Out-GridView -Title $title 330 | } 331 | else 332 | { 333 | Write-Warning "No differences detected between `"$($differentFile.file1)`" and `"$($differentFile.file2)`"" 334 | } 335 | } 336 | } 337 | else 338 | { 339 | Write-Warning "Can only compare when there are two files: `"$($differentFile.file1)`" and `"$($differentFile.file2)`"" 340 | } 341 | } 342 | } 343 | } 344 | else 345 | { 346 | $results 347 | } 348 | -------------------------------------------------------------------------------- /DNS Impostor Checker.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | Check DNS servers, using forward and reverse lookups, reports the same machine name as passed via IPv4 addresses. 5 | 6 | .DESCRIPTION 7 | 8 | Help find stale DNS entries for machines which no longer exist but still have DNS entries with the same IP address as running machines so appear to respond to ping, etc 9 | 10 | .PARAMETER computers 11 | 12 | List of computers to check 13 | 14 | .PARAMETER server 15 | 16 | DNS server to query otherwise use whatever has been configured 17 | 18 | .PARAMETER quiet 19 | 20 | Do not report any errors or warnings 21 | 22 | .EXAMPLE 23 | 24 | & '.\DNS Impostor Checker.ps1' -computers 'grl-dc03','grl-sql017' 25 | 26 | Check that the configured DNS server reports that the IP addresses for machines grl-dc03 and grl-sql017 belong to the same machines 27 | #> 28 | 29 | [CmdletBinding()] 30 | 31 | Param 32 | ( 33 | [Parameter(Mandatory,HelpMessage='Computers to check')] 34 | [string[]]$computers , 35 | [string]$server , 36 | [switch]$quiet 37 | ) 38 | 39 | [int]$counter = 0 40 | [int]$bad = 0 41 | 42 | [hashtable]$DNSoptions = @{} 43 | 44 | if( $PSBoundParameters[ 'server' ] ) 45 | { 46 | $DNSoptions.Add( 'Server' , $server ) 47 | } 48 | 49 | if( $quiet ) 50 | { 51 | $DNSoptions.Add( 'ErrorAction' , 'SilentlyContinue' ) 52 | } 53 | 54 | ForEach( $computer in $computers ) 55 | { 56 | $counter++ 57 | Write-Verbose -Message "$counter / $($computers.Count) : $computer" 58 | 59 | ## cope with multiple NICs/addresses 60 | if( ( [array]$addresses = @( Resolve-DnsName -Name $computer -Verbose:$false @DNSoptions ) ) -and $addresses.Count ) 61 | { 62 | [int]$addressCounter = 0 63 | ForEach( $address in $addresses ) 64 | { 65 | $addressCounter++ 66 | Write-Verbose -Message "`t$addressCounter / $($addresses.Count) : $($address.IPAddress)" 67 | if( ! ( $name = Resolve-DnsName -Name $address.IPAddress -Verbose:$false @DNSoptions ) -or $name.NameHost -ne $address.Name ) 68 | { 69 | $bad++ 70 | if( ! $quiet ) 71 | { 72 | if( $name ) 73 | { 74 | Write-Warning "$computer ($($address.IPAddress)) masquerading as $($name.Namehost)" 75 | } 76 | else 77 | { 78 | Write-Warning "$computer ($($address.IPAddress)) not found" 79 | } 80 | } 81 | } 82 | } 83 | } 84 | else ## failed to resolve 85 | { 86 | $bad++ 87 | } 88 | } 89 | 90 | Exit $bad 91 | -------------------------------------------------------------------------------- /Find JSON attribute by name.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | Find a given attribute by its name in a JSON structure 5 | 6 | .DESCRIPTION 7 | 8 | By default will search for a value name with the exact name specified but this can be switched to a regular expression match. 9 | By default will return the first match it finds but this can be overridden 10 | 11 | .PARAMETER inputObject 12 | 13 | The JSON object to search for the given attribute name 14 | 15 | .PARAMETER name 16 | 17 | The attribute name to search for 18 | 19 | .PARAMETER regex 20 | 21 | The -name parameter becomes a regular expression to match against each attribute name 22 | 23 | .PARAMETER multiple 24 | 25 | Return every matching result rather than the first encountered 26 | 27 | .EXAMPLE 28 | 29 | Get-JSONAttribute -inputObject $someJSON -name username 30 | 31 | Search the JSON object contained in $someJSON and return the first attribute where the name equals 'username' 32 | 33 | .EXAMPLE 34 | 35 | Get-JSONAttribute -inputObject $someJSON -name connection -regex -multiple 36 | 37 | Search the JSON object contained in $someJSON and return all attributes whose name matches 'connection' 38 | 39 | #> 40 | 41 | Function Get-JSONAttribute 42 | { 43 | [CmdletBinding()] 44 | 45 | Param 46 | ( 47 | [Parameter(ValueFromPipeline,Mandatory=$true,HelpMessage='JSON object to search')] 48 | $inputObject , 49 | [Parameter(Mandatory=$true,HelpMessage='JSON property name to search for')] 50 | [string]$name , 51 | [switch]$multiple , 52 | [switch]$regex 53 | ) 54 | 55 | $foundIt = $null 56 | 57 | If( $inputObject -and ! [string]::IsNullOrEmpty( $name ) ) 58 | { 59 | ForEach( $property in $inputObject.PSObject.Properties ) 60 | { 61 | If( $property.MemberType.ToString() -eq 'NoteProperty' ) 62 | { 63 | If( ( ! $regex -and $property.Name -eq $name ) -or ( $regex -and $property.Name -match $name ) ) 64 | { 65 | Return $property 66 | } 67 | Elseif( $property.Value -is [PSCustomObject] ) 68 | { 69 | If( ( $multiple -or ! $foundIt ) -and ( $result = Get-JSONAttribute -name $name -inputObject $property.value -multiple:$multiple -regex:$regex )) 70 | { 71 | $foundIt = $result 72 | $result 73 | } 74 | } 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /Find file type.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 3 2 | <# 3 | Examine files to see what type they really are 4 | 5 | @guyrleech 2019 6 | 7 | Modification History: 8 | 9 | 17/03/19 GRL Added new file types 10 | 18/03/19 GRL Updated msi definition and added doc as are only different at byte 26 11 | Added ace file definition 12 | Added extra property to show when file extension does not match actual type 13 | #> 14 | 15 | <# 16 | .SYNOPSIS 17 | 18 | Look at content of specifid files or files in specified folders and show their actual type based on this content. 19 | 20 | .DESCRIPTION 21 | 22 | Some file formats, such as Microsoft Office .docx and .xlsx are actually zip files so this script will identify a file's type based on its contents not file extension. 23 | Pipe to Out-Gridview for an on screen sortable/filterable window or to Export-CSV to record to file. 24 | 25 | .PARAMETER files 26 | 27 | A comma separated list of files to process 28 | 29 | .PARAMETER folders 30 | 31 | A comma separated list of folders to process 32 | 33 | .PARAMETER recurse 34 | 35 | Recurse each specified folder otherwise just process items in each folder 36 | 37 | .PARAMETER list 38 | 39 | List the file types that this script will detect 40 | 41 | .PARAMETER types 42 | 43 | Only include file types which match this regular expression 44 | 45 | .PARAMETER others 46 | 47 | Include files which have not been identified by their content 48 | 49 | .PARAMETER adfs 50 | 51 | Also include files which contain files in NTFS alternate data streams 52 | 53 | .PARAMETER onlyAdfs 54 | 55 | Only include files which contain files in NTFS alternate data streams 56 | 57 | .PARAMETER summary 58 | 59 | Display a summary with the count of each file type, sorted by the most prevalent 60 | 61 | .PARAMETER mismatched 62 | 63 | Only output files where the file type discovered does not match the extension of the file 64 | 65 | .EXAMPLE 66 | 67 | & '.\Find file type.ps1' -list 68 | 69 | Show all the file types that the script can identify 70 | 71 | .EXAMPLE 72 | 73 | & '.\Find file type.ps1' -file c:\documents\somedocument.docx 74 | 75 | Analyse the file c:\documents\somedocument.docx and if its contents match one of the known types then output this type it otherwise no output is produced 76 | 77 | .EXAMPLE 78 | 79 | & '.\Find file type.ps1' -folders c:\stuff -adfs -others -recurse 80 | 81 | Analyse all files in c:\stuff including subfolders and any with alternate data streams. Files which don't match a known type via their content will also be output 82 | 83 | .EXAMPLE 84 | 85 | & '.\Find file type.ps1' -folders c:\stuff -onlyAdfs -recurse 86 | 87 | Only analyse files in c:\stuff, and subfolders, with alternate data streams. 88 | 89 | .EXAMPLE 90 | 91 | & '.\Find file type.ps1' -folders c:\stuff -recurse -summary -types 'zip|rar' 92 | 93 | Analyse all files in c:\stuff including subfolders and produce a summary of the file types which match 'zip' such as "pkzip" or "7zip" or 'rar' 94 | 95 | .EXAMPLE 96 | 97 | & '.\Find file type.ps1' -folders c:\stuff -recurse -mismtached 98 | 99 | Analyse all files in c:\stuff including subfolders and output those file names where the file extension does not match the discovered file type 100 | 101 | #> 102 | 103 | [CmdletBinding()] 104 | 105 | Param 106 | ( 107 | [Parameter(ParameterSetName='files',Mandatory=$true)] 108 | [string[]]$files , 109 | [Parameter(ParameterSetName='folders',Mandatory=$true)] 110 | [string[]]$folders , 111 | [Parameter(ParameterSetName='folders',Mandatory=$false)] 112 | [switch]$recurse , 113 | [switch]$list , 114 | [string]$types , 115 | [switch]$others , 116 | [switch]$adfs , 117 | [switch]$onlyAdfs , 118 | [switch]$summary , 119 | [switch]$mismatched 120 | ) 121 | 122 | ## a great resource for information on headers of files can be found at http://file-extension.net/seeker/ 123 | 124 | [array]$magicNumbers = @( 125 | [pscustomobject]@{ 'Type' = 'ace' ; 'Offset' = 7 ; 'Extensions' = @( 'ace' ) ; 'Bytes' = @( 0x2A , 0x2A , 0x41, 0x43 , 0x45 , 0x2A , 0x2A) } 126 | [pscustomobject]@{ 'Type' = 'msi/msp' ; 'Offset' = 0 ; 'Extensions' = @( 'msi' , 'msp' ) ; 'Bytes' = @( 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x04, 0x00 )} 127 | [pscustomobject]@{ 'Type' = 'doc' ; 'Offset' = 0 ; 'Extensions' = @( 'doc' ) ; 'Bytes' = @( 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00 )} 128 | [pscustomobject]@{ 'Type' = 'zip' ; 'Offset' = 0 ; 'Extensions' = @( 'zip' , 'jar' ) ; 'Bytes' = @( 0x50 , 0x4B , 0x03 , 0x04 ) } 129 | [pscustomobject]@{ 'Type' = 'exe' ; 'Offset' = 0 ; 'Extensions' = @( 'exe' , 'dll' , 'ocx' , 'cpl' , 'scr' , 'mui' ) ; 'Bytes' = @( 0x4D , 0x5A ) } 130 | [pscustomobject]@{ 'Type' = 'jpg' ; 'Offset' = 6 ; 'Extensions' = @( 'jpg' , 'jpeg' , 'jpg_large' ) ; 'Bytes' = @( 0x4A , 0x46 , 0x49 , 0x46 ) } 131 | [pscustomobject]@{ 'Type' = 'jpg' ; 'Offset' = 6 ; 'Extensions' = @( 'jpg' , 'jpeg' , 'jpg_large' ) ; 'Bytes' = @( 0x45 , 0x78 , 0x69 , 0x66 ) } 132 | [pscustomobject]@{ 'Type' = 'jpg' ; 'Offset' = 6 ; 'Extensions' = @( 'jpg' , 'jpeg' , 'jpg_large' ) ; 'Bytes' = @( 0xFF , 0xD8 , 0xFF ) } 133 | [pscustomobject]@{ 'Type' = 'pdf' ; 'Offset' = 0 ; 'Extensions' = @( 'pdf' ) ; 'Bytes' = @( 0x25 , 0x50 , 0x44 , 0x46 , 0x2D) } 134 | [pscustomobject]@{ 'Type' = 'png' ; 'Offset' = 0 ; 'Extensions' = @( 'png' ) ; 'Bytes' = @( 0x89 , 0x50 , 0x4e , 0x47 ) } 135 | [pscustomobject]@{ 'Type' = 'mp4' ; 'Offset' = 4 ; 'Extensions' = @( 'mp4' ) ; 'Bytes' = @( 0x66 , 0x74 , 0x79 , 0x70 , 0x6D ) } 136 | [pscustomobject]@{ 'Type' = 'mp4' ; 'Offset' = 4 ; 'Extensions' = @( 'mp4' ) ; 'Bytes' = @( 0x66 , 0x74 , 0x79 , 0x70 , 0x69 ) } 137 | [pscustomobject]@{ 'Type' = 'bmp' ; 'Offset' = 0 ; 'Extensions' = @( 'bmp' ) ; 'Bytes' = @( 0x42 , 0x4d ) } 138 | [pscustomobject]@{ 'Type' = 'gif' ; 'Offset' = 0 ; 'Extensions' = @( 'gif' ) ; 'Bytes' = @( 0x47 , 0x49 , 0x46 ) } 139 | [pscustomobject]@{ 'Type' = 'wav' ; 'Offset' = 8 ; 'Extensions' = @( 'wav' ) ; 'Bytes' = @( 0x57 , 0x41 , 0x56 , 0x45 ) } 140 | [pscustomobject]@{ 'Type' = 'avi' ; 'Offset' = 8 ; 'Extensions' = @( 'avi' ) ; 'Bytes' = @( 0x41 , 0x56 , 0x49 ) } 141 | [pscustomobject]@{ 'Type' = 'gif' ; 'Offset' = 0 ; 'Extensions' = @( 'gif' ) ; 'Bytes' = @( 0x52 , 0x61 , 0x72 , 0x21 , 0x1A , 0x07 , 0x00 ) } 142 | [pscustomobject]@{ 'Type' = 'gzip' ; 'Offset' = 0 ; 'Extensions' = @( 'gz' , 'gzip' ) ; 'Bytes' = @( 0x1f , 0x8B ) } 143 | [pscustomobject]@{ 'Type' = '7zip' ; 'Offset' = 0 ; 'Extensions' = @( '7z' ) ; 'Bytes' = @( 0x37 , 0x7A ) } 144 | [pscustomobject]@{ 'Type' = 'rar' ; 'Offset' = 0 ; 'Extensions' = @( 'rar' ) ; 'Bytes' = @( 0x52, 0x61, 0x72, 0x21, 0x1A ) } 145 | [pscustomobject]@{ 'Type' = 'cab' ; 'Offset' = 0 ; 'Extensions' = @( 'cab' ) ; 'Bytes' = @( 0x49, 0x53, 0x63, 0x28 ) } 146 | [pscustomobject]@{ 'Type' = 'tif' ; 'Offset' = 0 ; 'Extensions' = @( 'tiff' , 'tif' ) ; 'Bytes' = @( 0x49, 0x49, 0x2A, 0x00 ) } 147 | [pscustomobject]@{ 'Type' = 'tif' ; 'Offset' = 0 ; 'Extensions' = @( 'tiff' , 'tif' ) ; 'Bytes' = @( 0x4D, 0x4D, 0x00 ) } 148 | [pscustomobject]@{ 'Type' = 'evtx' ; 'Offset' = 0 ; 'Extensions' = @( 'evtx' ) ; 'Bytes' = @( 0x45 , 0x6C , 0x66 , 0x46 , 0x69 , 0x6C , 0x65 , 0x00 ) } 149 | [pscustomobject]@{ 'Type' = 'cab' ; 'Offset' = 0 ; 'Extensions' = @( 'cab' ) ; 'Bytes' = @( 0x4D, 0x53, 0x43, 0x46, 0x00, 0x00, 0x00, 0x00 ) } 150 | [pscustomobject]@{ 'Type' = 'wim' ; 'Offset' = 0 ; 'Extensions' = @( 'wim' ) ; 'Bytes' = @( 0x4D , 0x53 , 0x57 , 0x49 , 0x4D ) } 151 | [pscustomobject]@{ 'Type' = 'mkv' ; 'Offset' = 0 ; 'Extensions' = @( 'mkv' ) ; 'Bytes' = @( 0x1A , 0x45 , 0xDF , 0xA3 ) } 152 | [pscustomobject]@{ 'Type' = 'wmv/wma' ; 'Offset' = 0 ; 'Extensions' = @( 'wmv' , 'wma' ) ; 'Bytes' = @( 0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00 ) } 153 | [pscustomobject]@{ 'Type' = 'vhdx' ; 'Offset' = 0 ; 'Extensions' = @( 'vhdx' ) ; 'Bytes' = @( 0x76, 0x68, 0x64, 0x78, 0x66, 0x69, 0x6C, 0x65 )} 154 | [pscustomobject]@{ 'Type' = 'vhd' ; 'Offset' = 0 ; 'Extensions' = @( 'vhd' ) ; 'Bytes' = @( 0x63, 0x6F, 0x6E, 0x65, 0x63, 0x74, 0x69, 0x78 )} 155 | ## uncomment and use with verbose to show hex bytes found for each file to help analyse file 156 | ##[pscustomobject]@{ 'Type' = 'dummy' ; 'Offset' = 32 ; 'Bytes' = @( 0xDE , 0xAD , 0xBF , 0xBE ) } 157 | ) 158 | 159 | Function Get-FileType 160 | { 161 | Param 162 | ( 163 | [Parameter(Mandatory=$true)] 164 | [string]$file , 165 | [AllowEmptyString()] 166 | [AllowNull()] 167 | [string]$types , 168 | [long]$length , 169 | [switch]$mismatched , 170 | [int]$maxHeaderLength ### including offset 171 | ) 172 | 173 | ## Slower than using streams but streams don't work with alternate data streams 174 | $fileError = $null 175 | [byte[]]$bytes = Get-Content -LiteralPath $file -TotalCount $maxHeaderLength -Encoding Byte -ErrorAction SilentlyContinue -ErrorVariable $fileError 176 | 177 | if( $bytes ) 178 | { 179 | if( $VerbosePreference -eq 'Continue' ) 180 | { 181 | Write-Verbose "$file : $($bytes | ForEach-Object { "0x$($_.ToString('X2'))," } )" 182 | } 183 | [bool]$matched = $false 184 | ForEach( $magicNumber in $magicNumbers ) 185 | { 186 | [bool]$match = $true 187 | For( [int]$index = $magicNumber.Offset ; $index -lt $magicNumber.Offset + $magicNumber.Bytes.Count -and $index -lt $bytes.Count ; $index++ ) 188 | { 189 | if( $bytes[ $index ] -ne $magicNumber.Bytes[ $index - $magicNumber.Offset ] ) 190 | { 191 | $match = $false 192 | break 193 | } 194 | } 195 | if( $match -and $index -le $bytes.Count ) 196 | { 197 | if( ! $types -or $magicNumber.Type -match $types ) 198 | { 199 | [string]$extension = [System.IO.Path]::GetExtension( $file ) -replace '^\.' , '' 200 | [bool]$mismatch = ( $extension -notin $magicNumber.Extensions ) 201 | if( ! $mismatched -or $mismatch ) 202 | { 203 | [pscustomobject]@{ 'File' = $file ; 'Type' = $magicNumber.Type ; 'Extension' = $extension ; 'Size (KB)' = [int]( $length / 1KB ) ; 'Mismatch' = $(if ( $mismatch ) { 'Yes' } else { 'No' } )} 204 | } 205 | } 206 | ## else not interested in this type 207 | $matched = $true 208 | break 209 | } 210 | } 211 | if( ! $matched -and $others ) 212 | { 213 | [pscustomobject]@{ 'File' = $file ; 'Type' = $null ; 'Extension' = [System.IO.Path]::GetExtension( $file ) ; 'Size (KB)' = [int]( $length / 1KB ) ; 'Mismatch' = 'No'} 214 | } 215 | } 216 | elseif( $fileError ) 217 | { 218 | Write-Warning "Failed to read from `"$file`" : $filError" 219 | } 220 | } 221 | 222 | if( $onlyAdfs ) 223 | { 224 | $adfs = $true 225 | } 226 | 227 | if( $others -and $mismatched ) 228 | { 229 | Write-Error 'Cannot output mismatched only when reporting on unmatched (other) files' 230 | } 231 | 232 | [int]$maxHeaderLength = 0 233 | 234 | if( $summary -and [string]::IsNullOrEmpty( $types ) ) 235 | { 236 | Throw 'Must specify the type to summarise via the -types argument' 237 | } 238 | 239 | ForEach( $magicNumber in $magicNumbers ) 240 | { 241 | if( $magicNumber.Bytes.Count + $magicNumber.Offset -gt $maxHeaderLength ) 242 | { 243 | $maxHeaderLength = $magicNumber.Bytes.Count + $magicNumber.Offset 244 | } 245 | } 246 | 247 | if( $list ) 248 | { 249 | $magicNumbers|Select -ExpandProperty Type|sort -Unique 250 | } 251 | else 252 | { 253 | $results = @( if( $PSCmdlet.ParameterSetName -eq 'folders' ) 254 | { 255 | [hashtable]$params = @{ 'Path' = $folders ; 'File' = $true } 256 | $params.Add( 'Recurse' , $recurse ) 257 | Get-ChildItem @params | ForEach-Object ` 258 | { 259 | if( ! $onlyAdfs ) 260 | { 261 | Get-FileType -file $_.FullName -maxHeaderLength $maxHeaderLength -type $types -length $_.Length -mismatched:$mismatched 262 | } 263 | if( $adfs ) 264 | { 265 | try 266 | { 267 | $file = $_ 268 | ## $DATA is the file content itself 269 | Get-Item -LiteralPath $file.FullName -Stream '*' | Where-Object { $_.Stream -ne ':$DATA' } | ForEach-Object ` 270 | { 271 | $alternate = $_ 272 | if( ! [string]::IsNullOrEmpty( $alternate.Stream ) ) 273 | { 274 | [string]$streamPath = ( $file.FullName + ':' + $alternate.Stream ) 275 | $item = Get-Item -LiteralPath $streamPath 276 | if( $item ) 277 | { 278 | Get-FileType -file $streamPath -maxHeaderLength $maxHeaderLength -type $types -Length $item.Length -mismatched:$mismatched 279 | } 280 | } 281 | } 282 | } 283 | catch 284 | { 285 | Write-Error "$($file.FullName) : $_" 286 | } 287 | } 288 | } 289 | } 290 | else 291 | { 292 | ForEach( $file in $files ) 293 | { 294 | $item = Get-Item -LiteralPath $file 295 | if( $item ) 296 | { 297 | Get-FileType -file $file -maxHeaderLength $maxHeaderLength -type $types -length $item.Length -mismatched:$mismatched 298 | } 299 | } 300 | }) 301 | 302 | if( $summary ) 303 | { 304 | $groupByExtension = $results | Group-Object -Property 'Extension' 305 | $groupByExtension | Select-Object -Property Name,Count | Sort-Object -Property Count -Descending 306 | } 307 | else 308 | { 309 | $results 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /Get chunk at offset.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | Read specific amount of string data from a (log) file at a given offset like you might see being written in a SysInternals procmon trace 5 | 6 | .PARAMETER path 7 | 8 | The (log) file to read and display text data from 9 | 10 | .PARAMETER offset 11 | 12 | The offset in bytes from the start of the file to read data from 13 | 14 | .PARAMETER count 15 | 16 | The number of bytes to read from the given offset 17 | 18 | .PARAMETER procmonDetail 19 | 20 | The text from the "Detail" column in procmon for the given write to the (log) file. E.g. "Offset: 10,014,174, Length: 18", where you would right click the Detail column on the required line in procmon and select "Copy" 21 | 22 | .PARAMETER minimumCount 23 | 24 | If greater than the length from the -procmonDetail string it will display this much of the log file starting from the offset rather than what was found in the "length" string. Use to display more than one line 25 | 26 | .EXAMPLE 27 | 28 | & '.\Get chunk at offset.ps1' -path 'C:\temp\EMLogs\EmUser_session_1_Pid-9200_8.49.log" -minimumCount 1KB -procmonDetail 'Offset: 12,798,606, Length: 55' 29 | 30 | Read 1KB of data starting at offset 12798606 in the specified file and show 1KB of this where the string containing "Offset" and "Length" have been copied from the detail column of a specific line of a procmon trace 31 | 32 | .EXAMPLE 33 | 34 | & '.\Get chunk at offset.ps1' -path 'C:\temp\EMLogs\EmUser_session_1_Pid-9200_8.49.log" -count 1KB -offset 12798606 35 | 36 | Read 1KB of data starting at offset 12798606 in the specified file and show 1KB of this 37 | 38 | .NOTES 39 | 40 | Modification History: 41 | 42 | 12/06/20 @guyrleech Bug fix procmon detail regex & add test for offset too large 43 | 04/08/22 @guyrleech Added facility to copy file if cannot be accessed because busy 44 | #> 45 | 46 | [CmdletBinding()] 47 | 48 | Param 49 | ( 50 | [Parameter(Mandatory=$true,HelpMessage='Test log file to read')] 51 | [string]$path , 52 | [Parameter(Mandatory=$true,ParameterSetName='OffsetAndSize')] 53 | [long]$offset , 54 | [Parameter(Mandatory=$false,ParameterSetName='OffsetAndSize')] 55 | [long]$count = 1KB, 56 | [Parameter(Mandatory=$true,ParameterSetName='FromProcmon')] 57 | [string]$procmonDetail , 58 | [Parameter(Mandatory=$false,ParameterSetName='FromProcmon')] 59 | [long]$minimumCount , 60 | [switch]$makeCopy 61 | ) 62 | 63 | $fileCopy = $nulll 64 | $fileStream = $null 65 | 66 | try 67 | { 68 | $fileStream = New-Object -TypeName System.IO.FileStream( $path , [System.IO.FileMode]::Open , [System.IO.FileAccess]::Read ) 69 | } 70 | catch 71 | { 72 | if( $makeCopy -and $_.Exception -and $_.Exception.GetBaseException() -match 'The process cannot access the file .* because it is being used by another process' ) 73 | { 74 | if( $fileCopy = New-TemporaryFile ) 75 | { 76 | Copy-Item -Path $path -Destination $filecopy.FullName -Force 77 | if( $? ) 78 | { 79 | $fileStream = New-Object -TypeName System.IO.FileStream( $fileCopy , [System.IO.FileMode]::Open , [System.IO.FileAccess]::Read ) 80 | } 81 | else 82 | { 83 | Remove-Item -Path $fileCopy -Force -ErrorAction SilentlyContinue 84 | Throw "File is busy but unable to take copy to $($fileCopy.FullName)" 85 | } 86 | } 87 | else 88 | { 89 | Throw "File is busy but unable to take a copy because couldn't get a temp file" 90 | } 91 | } 92 | else 93 | { 94 | Throw $_ 95 | } 96 | } 97 | 98 | try 99 | { 100 | if( $filestream ) 101 | { 102 | if( $PSBoundParameters[ 'procmonDetail' ] ) 103 | { 104 | ## Offset: 10,014,174, Length: 18 105 | if( $procmonDetail -match 'Offset:\s*([\d,]*).*Length:\s*([\d,]*)' ) 106 | { 107 | ## [int] conversion copes with commas so no need to remove 108 | $offset = $Matches[1] -as [int] 109 | $count = $Matches[2] -as [int] 110 | if( $PSBoundParameters[ 'minimumCount' ] -and $count -lt $minimumCount ) 111 | { 112 | $count = $minimumCount 113 | } 114 | } 115 | else 116 | { 117 | Throw "Unexpected procmon detail format `"$procmonDetail`"" 118 | } 119 | } 120 | 121 | Write-Verbose -Message "Seeking to $offset and reading $count bytes" 122 | 123 | if( $offset -gt $fileStream.Length ) 124 | { 125 | Throw "Offset $offset is too large, file is only $($fileStream.Length) bytes" 126 | } 127 | 128 | $null = $fileStream.Seek( $offset , [System.IO.SeekOrigin]::Begin ) 129 | 130 | $chunk = New-Object byte[] $count 131 | 132 | [int]$read = $fileStream.Read( $chunk , 0 , $count ) 133 | 134 | if( $read -ne $count ) 135 | { 136 | Write-Warning "Read $read not $count bytes" 137 | } 138 | 139 | $fileStream.Close() 140 | 141 | $fileStream = $null 142 | 143 | [System.Text.Encoding]::ASCII.GetString( $chunk ) 144 | } 145 | } 146 | catch 147 | { 148 | Throw $_ 149 | } 150 | finally 151 | { 152 | if( $fileCopy ) 153 | { 154 | Remove-Item -Path $fileCopy -Force 155 | } 156 | } 157 | 158 | # SIG # Begin signature block 159 | # MIIjdgYJKoZIhvcNAQcCoIIjZzCCI2MCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB 160 | # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR 161 | # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQULvGGNnE3vmalBtFaMyc8wIyA 162 | # u8iggh2UMIIFMDCCBBigAwIBAgIQBAkYG1/Vu2Z1U0O1b5VQCDANBgkqhkiG9w0B 163 | # AQsFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD 164 | # VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk 165 | # IElEIFJvb3QgQ0EwHhcNMTMxMDIyMTIwMDAwWhcNMjgxMDIyMTIwMDAwWjByMQsw 166 | # CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu 167 | # ZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQg 168 | # Q29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA 169 | # +NOzHH8OEa9ndwfTCzFJGc/Q+0WZsTrbRPV/5aid2zLXcep2nQUut4/6kkPApfmJ 170 | # 1DcZ17aq8JyGpdglrA55KDp+6dFn08b7KSfH03sjlOSRI5aQd4L5oYQjZhJUM1B0 171 | # sSgmuyRpwsJS8hRniolF1C2ho+mILCCVrhxKhwjfDPXiTWAYvqrEsq5wMWYzcT6s 172 | # cKKrzn/pfMuSoeU7MRzP6vIK5Fe7SrXpdOYr/mzLfnQ5Ng2Q7+S1TqSp6moKq4Tz 173 | # rGdOtcT3jNEgJSPrCGQ+UpbB8g8S9MWOD8Gi6CxR93O8vYWxYoNzQYIH5DiLanMg 174 | # 0A9kczyen6Yzqf0Z3yWT0QIDAQABo4IBzTCCAckwEgYDVR0TAQH/BAgwBgEB/wIB 175 | # ADAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMweQYIKwYBBQUH 176 | # AQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYI 177 | # KwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFz 178 | # c3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmw0 179 | # LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaG 180 | # NGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RD 181 | # QS5jcmwwTwYDVR0gBEgwRjA4BgpghkgBhv1sAAIEMCowKAYIKwYBBQUHAgEWHGh0 182 | # dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCgYIYIZIAYb9bAMwHQYDVR0OBBYE 183 | # FFrEuXsqCqOl6nEDwGD5LfZldQ5YMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6en 184 | # IZ3zbcgPMA0GCSqGSIb3DQEBCwUAA4IBAQA+7A1aJLPzItEVyCx8JSl2qB1dHC06 185 | # GsTvMGHXfgtg/cM9D8Svi/3vKt8gVTew4fbRknUPUbRupY5a4l4kgU4QpO4/cY5j 186 | # DhNLrddfRHnzNhQGivecRk5c/5CxGwcOkRX7uq+1UcKNJK4kxscnKqEpKBo6cSgC 187 | # PC6Ro8AlEeKcFEehemhor5unXCBc2XGxDI+7qPjFEmifz0DLQESlE/DmZAwlCEIy 188 | # sjaKJAL+L3J+HNdJRZboWR3p+nRka7LrZkPas7CM1ekN3fYBIM6ZMWM9CBoYs4Gb 189 | # T8aTEAb8B4H6i9r5gkn3Ym6hU/oSlBiFLpKR6mhsRDKyZqHnGKSaZFHvMIIFTzCC 190 | # BDegAwIBAgIQBP3jqtvdtaueQfTZ1SF1TjANBgkqhkiG9w0BAQsFADByMQswCQYD 191 | # VQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGln 192 | # aWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgQ29k 193 | # ZSBTaWduaW5nIENBMB4XDTIwMDcyMDAwMDAwMFoXDTIzMDcyNTEyMDAwMFowgYsx 194 | # CzAJBgNVBAYTAkdCMRIwEAYDVQQHEwlXYWtlZmllbGQxJjAkBgNVBAoTHVNlY3Vy 195 | # ZSBQbGF0Zm9ybSBTb2x1dGlvbnMgTHRkMRgwFgYDVQQLEw9TY3JpcHRpbmdIZWF2 196 | # ZW4xJjAkBgNVBAMTHVNlY3VyZSBQbGF0Zm9ybSBTb2x1dGlvbnMgTHRkMIIBIjAN 197 | # BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr20nXdaAALva07XZykpRlijxfIPk 198 | # TUQFAxQgXTW2G5Jc1YQfIYjIePC6oaD+3Zc2WN2Jrsc7bj5Qe5Nj4QHHHf3jopLy 199 | # g8jXl7Emt1mlyzUrtygoQ1XpBBXnv70dvZibro6dXmK8/M37w5pEAj/69+AYM7IO 200 | # Fz2CrTIrQjvwjELSOkZ2o+z+iqfax9Z1Tv82+yg9iDHnUxZWhaiEXk9BFRv9WYsz 201 | # qTXQTEhv8fmUI2aZX48so4mJhNGu7Vp1TGeCik1G959Qk7sFh3yvRugjY0IIXBXu 202 | # A+LRT00yjkgMe8XoDdaBoIn5y3ZrQ7bCVDjoTrcn/SqfHvhEEMj1a1f0zQIDAQAB 203 | # o4IBxTCCAcEwHwYDVR0jBBgwFoAUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0O 204 | # BBYEFE16ovlqIk5uX2JQy6og0OCPrsnJMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE 205 | # DDAKBggrBgEFBQcDAzB3BgNVHR8EcDBuMDWgM6Axhi9odHRwOi8vY3JsMy5kaWdp 206 | # Y2VydC5jb20vc2hhMi1hc3N1cmVkLWNzLWcxLmNybDA1oDOgMYYvaHR0cDovL2Ny 207 | # bDQuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwTAYDVR0gBEUw 208 | # QzA3BglghkgBhv1sAwEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNl 209 | # cnQuY29tL0NQUzAIBgZngQwBBAEwgYQGCCsGAQUFBwEBBHgwdjAkBggrBgEFBQcw 210 | # AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tME4GCCsGAQUFBzAChkJodHRwOi8v 211 | # Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNzdXJlZElEQ29kZVNp 212 | # Z25pbmdDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAU9zO 213 | # 9UpTkPL8DNrcbIaf1w736CgWB5KRQsmp1mhXbGECUCCpOCzlYFCSeiwH9MT0je3W 214 | # aYxWqIpUMvAI8ndFPVDp5RF+IJNifs+YuLBcSv1tilNY+kfa2OS20nFrbFfl9QbR 215 | # 4oacz8sBhhOXrYeUOU4sTHSPQjd3lpyhhZGNd3COvc2csk55JG/h2hR2fK+m4p7z 216 | # sszK+vfqEX9Ab/7gYMgSo65hhFMSWcvtNO325mAxHJYJ1k9XEUTmq828ZmfEeyMq 217 | # K9FlN5ykYJMWp/vK8w4c6WXbYCBXWL43jnPyKT4tpiOjWOI6g18JMdUxCG41Hawp 218 | # hH44QHzE1NPeC+1UjTCCBY0wggR1oAMCAQICEA6bGI750C3n79tQ4ghAGFowDQYJ 219 | # KoZIhvcNAQEMBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IElu 220 | # YzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQg 221 | # QXNzdXJlZCBJRCBSb290IENBMB4XDTIyMDgwMTAwMDAwMFoXDTMxMTEwOTIzNTk1 222 | # OVowYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UE 223 | # CxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBS 224 | # b290IEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv+aQc2jeu+Rd 225 | # SjwwIjBpM+zCpyUuySE98orYWcLhKac9WKt2ms2uexuEDcQwH/MbpDgW61bGl20d 226 | # q7J58soR0uRf1gU8Ug9SH8aeFaV+vp+pVxZZVXKvaJNwwrK6dZlqczKU0RBEEC7f 227 | # gvMHhOZ0O21x4i0MG+4g1ckgHWMpLc7sXk7Ik/ghYZs06wXGXuxbGrzryc/NrDRA 228 | # X7F6Zu53yEioZldXn1RYjgwrt0+nMNlW7sp7XeOtyU9e5TXnMcvak17cjo+A2raR 229 | # mECQecN4x7axxLVqGDgDEI3Y1DekLgV9iPWCPhCRcKtVgkEy19sEcypukQF8IUzU 230 | # vK4bA3VdeGbZOjFEmjNAvwjXWkmkwuapoGfdpCe8oU85tRFYF/ckXEaPZPfBaYh2 231 | # mHY9WV1CdoeJl2l6SPDgohIbZpp0yt5LHucOY67m1O+SkjqePdwA5EUlibaaRBkr 232 | # fsCUtNJhbesz2cXfSwQAzH0clcOP9yGyshG3u3/y1YxwLEFgqrFjGESVGnZifvaA 233 | # sPvoZKYz0YkH4b235kOkGLimdwHhD5QMIR2yVCkliWzlDlJRR3S+Jqy2QXXeeqxf 234 | # jT/JvNNBERJb5RBQ6zHFynIWIgnffEx1P2PsIV/EIFFrb7GrhotPwtZFX50g/KEe 235 | # xcCPorF+CiaZ9eRpL5gdLfXZqbId5RsCAwEAAaOCATowggE2MA8GA1UdEwEB/wQF 236 | # MAMBAf8wHQYDVR0OBBYEFOzX44LScV1kTN8uZz/nupiuHA9PMB8GA1UdIwQYMBaA 237 | # FEXroq/0ksuCMS1Ri6enIZ3zbcgPMA4GA1UdDwEB/wQEAwIBhjB5BggrBgEFBQcB 238 | # AQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggr 239 | # BgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNz 240 | # dXJlZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vY3JsMy5k 241 | # aWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMBEGA1UdIAQK 242 | # MAgwBgYEVR0gADANBgkqhkiG9w0BAQwFAAOCAQEAcKC/Q1xV5zhfoKN0Gz22Ftf3 243 | # v1cHvZqsoYcs7IVeqRq7IviHGmlUIu2kiHdtvRoU9BNKei8ttzjv9P+Aufih9/Jy 244 | # 3iS8UgPITtAq3votVs/59PesMHqai7Je1M/RQ0SbQyHrlnKhSLSZy51PpwYDE3cn 245 | # RNTnf+hZqPC/Lwum6fI0POz3A8eHqNJMQBk1RmppVLC4oVaO7KTVPeix3P0c2PR3 246 | # WlxUjG/voVA9/HYJaISfb8rbII01YBwCA8sgsKxYoA5AY8WYIsGyWfVVa88nq2x2 247 | # zm8jLfR+cWojayL/ErhULSd+2DrZ8LaHlv1b0VysGMNNn3O3AamfV6peKOK5lDCC 248 | # Bq4wggSWoAMCAQICEAc2N7ckVHzYR6z9KGYqXlswDQYJKoZIhvcNAQELBQAwYjEL 249 | # MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 250 | # LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0 251 | # MB4XDTIyMDMyMzAwMDAwMFoXDTM3MDMyMjIzNTk1OVowYzELMAkGA1UEBhMCVVMx 252 | # FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVz 253 | # dGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQTCCAiIwDQYJKoZI 254 | # hvcNAQEBBQADggIPADCCAgoCggIBAMaGNQZJs8E9cklRVcclA8TykTepl1Gh1tKD 255 | # 0Z5Mom2gsMyD+Vr2EaFEFUJfpIjzaPp985yJC3+dH54PMx9QEwsmc5Zt+FeoAn39 256 | # Q7SE2hHxc7Gz7iuAhIoiGN/r2j3EF3+rGSs+QtxnjupRPfDWVtTnKC3r07G1decf 257 | # BmWNlCnT2exp39mQh0YAe9tEQYncfGpXevA3eZ9drMvohGS0UvJ2R/dhgxndX7RU 258 | # CyFobjchu0CsX7LeSn3O9TkSZ+8OpWNs5KbFHc02DVzV5huowWR0QKfAcsW6Th+x 259 | # tVhNef7Xj3OTrCw54qVI1vCwMROpVymWJy71h6aPTnYVVSZwmCZ/oBpHIEPjQ2OA 260 | # e3VuJyWQmDo4EbP29p7mO1vsgd4iFNmCKseSv6De4z6ic/rnH1pslPJSlRErWHRA 261 | # KKtzQ87fSqEcazjFKfPKqpZzQmiftkaznTqj1QPgv/CiPMpC3BhIfxQ0z9JMq++b 262 | # Pf4OuGQq+nUoJEHtQr8FnGZJUlD0UfM2SU2LINIsVzV5K6jzRWC8I41Y99xh3pP+ 263 | # OcD5sjClTNfpmEpYPtMDiP6zj9NeS3YSUZPJjAw7W4oiqMEmCPkUEBIDfV8ju2Tj 264 | # Y+Cm4T72wnSyPx4JduyrXUZ14mCjWAkBKAAOhFTuzuldyF4wEr1GnrXTdrnSDmuZ 265 | # DNIztM2xAgMBAAGjggFdMIIBWTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW 266 | # BBS6FtltTYUvcyl2mi91jGogj57IbzAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/ 267 | # 57qYrhwPTzAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwdwYI 268 | # KwYBBQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j 269 | # b20wQQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp 270 | # Q2VydFRydXN0ZWRSb290RzQuY3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9j 271 | # cmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMCAGA1Ud 272 | # IAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAgEA 273 | # fVmOwJO2b5ipRCIBfmbW2CFC4bAYLhBNE88wU86/GPvHUF3iSyn7cIoNqilp/GnB 274 | # zx0H6T5gyNgL5Vxb122H+oQgJTQxZ822EpZvxFBMYh0MCIKoFr2pVs8Vc40BIiXO 275 | # lWk/R3f7cnQU1/+rT4osequFzUNf7WC2qk+RZp4snuCKrOX9jLxkJodskr2dfNBw 276 | # CnzvqLx1T7pa96kQsl3p/yhUifDVinF2ZdrM8HKjI/rAJ4JErpknG6skHibBt94q 277 | # 6/aesXmZgaNWhqsKRcnfxI2g55j7+6adcq/Ex8HBanHZxhOACcS2n82HhyS7T6NJ 278 | # uXdmkfFynOlLAlKnN36TU6w7HQhJD5TNOXrd/yVjmScsPT9rp/Fmw0HNT7ZAmyEh 279 | # QNC3EyTN3B14OuSereU0cZLXJmvkOHOrpgFPvT87eK1MrfvElXvtCl8zOYdBeHo4 280 | # 6Zzh3SP9HSjTx/no8Zhf+yvYfvJGnXUsHicsJttvFXseGYs2uJPU5vIXmVnKcPA3 281 | # v5gA3yAWTyf7YGcWoWa63VXAOimGsJigK+2VQbc61RWYMbRiCQ8KvYHZE/6/pNHz 282 | # V9m8BPqC3jLfBInwAM1dwvnQI38AC+R2AibZ8GV2QqYphwlHK+Z/GqSFD/yYlvZV 283 | # VCsfgPrA8g4r5db7qS9EFUrnEw4d2zc4GqEr9u3WfPwwggbGMIIErqADAgECAhAK 284 | # ekqInsmZQpAGYzhNhpedMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVTMRcw 285 | # FQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3Rl 286 | # ZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjIwMzI5MDAw 287 | # MDAwWhcNMzMwMzE0MjM1OTU5WjBMMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln 288 | # aUNlcnQsIEluYy4xJDAiBgNVBAMTG0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDIyIC0g 289 | # MjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALkqliOmXLxf1knwFYIY 290 | # 9DPuzFxs4+AlLtIx5DxArvurxON4XX5cNur1JY1Do4HrOGP5PIhp3jzSMFENMQe6 291 | # Rm7po0tI6IlBfw2y1vmE8Zg+C78KhBJxbKFiJgHTzsNs/aw7ftwqHKm9MMYW2Nq8 292 | # 67Lxg9GfzQnFuUFqRUIjQVr4YNNlLD5+Xr2Wp/D8sfT0KM9CeR87x5MHaGjlRDRS 293 | # Xw9Q3tRZLER0wDJHGVvimC6P0Mo//8ZnzzyTlU6E6XYYmJkRFMUrDKAz200kheiC 294 | # lOEvA+5/hQLJhuHVGBS3BEXz4Di9or16cZjsFef9LuzSmwCKrB2NO4Bo/tBZmCbO 295 | # 4O2ufyguwp7gC0vICNEyu4P6IzzZ/9KMu/dDI9/nw1oFYn5wLOUrsj1j6siugSBr 296 | # Q4nIfl+wGt0ZvZ90QQqvuY4J03ShL7BUdsGQT5TshmH/2xEvkgMwzjC3iw9dRLND 297 | # HSNQzZHXL537/M2xwafEDsTvQD4ZOgLUMalpoEn5deGb6GjkagyP6+SxIXuGZ1h+ 298 | # fx/oK+QUshbWgaHK2jCQa+5vdcCwNiayCDv/vb5/bBMY38ZtpHlJrYt/YYcFaPfU 299 | # cONCleieu5tLsuK2QT3nr6caKMmtYbCgQRgZTu1Hm2GV7T4LYVrqPnqYklHNP8lE 300 | # 54CLKUJy93my3YTqJ+7+fXprAgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4Aw 301 | # DAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAX 302 | # MAgGBmeBDAEEAjALBglghkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3Mpdpov 303 | # dYxqII+eyG8wHQYDVR0OBBYEFI1kt4kh/lZYRIRhp+pvHDaP3a8NMFoGA1UdHwRT 304 | # MFEwT6BNoEuGSWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0 305 | # ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEB 306 | # BIGDMIGAMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYI 307 | # KwYBBQUHMAKGTGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRy 308 | # dXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcN 309 | # AQELBQADggIBAA0tI3Sm0fX46kuZPwHk9gzkrxad2bOMl4IpnENvAS2rOLVwEb+E 310 | # GYs/XeWGT76TOt4qOVo5TtiEWaW8G5iq6Gzv0UhpGThbz4k5HXBw2U7fIyJs1d/2 311 | # WcuhwupMdsqh3KErlribVakaa33R9QIJT4LWpXOIxJiA3+5JlbezzMWn7g7h7x44 312 | # ip/vEckxSli23zh8y/pc9+RTv24KfH7X3pjVKWWJD6KcwGX0ASJlx+pedKZbNZJQ 313 | # fPQXpodkTz5GiRZjIGvL8nvQNeNKcEiptucdYL0EIhUlcAZyqUQ7aUcR0+7px6A+ 314 | # TxC5MDbk86ppCaiLfmSiZZQR+24y8fW7OK3NwJMR1TJ4Sks3KkzzXNy2hcC7cDBV 315 | # eNaY/lRtf3GpSBp43UZ3Lht6wDOK+EoojBKoc88t+dMj8p4Z4A2UKKDr2xpRoJWC 316 | # jihrpM6ddt6pc6pIallDrl/q+A8GQp3fBmiW/iqgdFtjZt5rLLh4qk1wbfAs8QcV 317 | # fjW05rUMopml1xVrNQ6F1uAszOAMJLh8UgsemXzvyMjFjFhpr6s94c/MfRWuFL+K 318 | # cd/Kl7HYR+ocheBFThIcFClYzG/Tf8u+wQ5KbyCcrtlzMlkI5y2SoRoR/jKYpl0r 319 | # l+CL05zMbbUNrkdjOEcXW28T2moQbh9Jt0RbtAgKh1pZBHYRoad3AhMcMYIFTDCC 320 | # BUgCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ 321 | # MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hB 322 | # MiBBc3N1cmVkIElEIENvZGUgU2lnbmluZyBDQQIQBP3jqtvdtaueQfTZ1SF1TjAJ 323 | # BgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0B 324 | # CQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAj 325 | # BgkqhkiG9w0BCQQxFgQUbpu7reKlJxl7tOipc7mrhfPAr34wDQYJKoZIhvcNAQEB 326 | # BQAEggEALzYfc0J0Hi4eqUaU/XTKsOeOOipcTRFnAmh1yGXtDyz+RxtpynTHAvL3 327 | # IhpIQDGHbhykmu8Vz5q+xiSJDzoUWjI/SuHigaQkYSnazkhqYgED9hYKRM+mbtuo 328 | # N7TK1CSfuRDb/U9jqo/LmrL96t4psTvICcnc6mw3W+RX0iMUoFiDaTUMOvE+GLv6 329 | # w3uctFh7EWr4+F3v3p8dsC0r+1QkDnPAtSibpOq683dheI2OIFOuIjVF7UTurY53 330 | # +wtF2ZDQxgl4gH+kTi4/6TdNKCtLwFy1SUooskNT9wI4abL5KNrmceMn1qx46b5A 331 | # EFRIev/ajJ1Bo50iZHzoMErVPXVI9KGCAyAwggMcBgkqhkiG9w0BCQYxggMNMIID 332 | # CQIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7 333 | # MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1l 334 | # U3RhbXBpbmcgQ0ECEAp6SoieyZlCkAZjOE2Gl50wDQYJYIZIAWUDBAIBBQCgaTAY 335 | # BgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yMjA4MDUw 336 | # OTU2MjJaMC8GCSqGSIb3DQEJBDEiBCAysT2p8aMng0u3Ydo1y98vom3+Qa0EaLyo 337 | # SJT1JGZUIjANBgkqhkiG9w0BAQEFAASCAgAYsQ1pB5tIUuI5fzScfV9OCYirLYjn 338 | # he7XE+4iTZu0XcVWz0du5MsVnfaj4vzohfiQ3u4CBE9IxvjP3Q4IIAz3BCyUL0C5 339 | # qrxVMTdiRkfFJsHOtcJLwRTMsoU8qVZo1o+JhQrkyXfFvj8fH8LqHz9tbclDnJnF 340 | # AU1NFeSZd1oWQAXFqDTCsGUOzikU448oIRoAgFBTCBi8Ls6Db0naaU/hdpie9uyE 341 | # mooOZjILfmBDttZH71K9p9OUIv7gRGqRlrEy8tRdpDsY3klrQ8XiF6UxL21MyPlR 342 | # utXIcyDXm7vpMa1fNieoBKkmlWc2HeLcrBsudqpHmvQWAQco2VP9oyHdnLmc7vJa 343 | # qR1CvOAzvacihriYk02bojYD/qfHjh2YI+/C27wU33HsbYPRNw24hxYKuw0VUsBZ 344 | # S35f6mIqdKubOLs9hxfQgIJPEi8cjDskSF8OhXDdn+hFtj6pc1OzXevHM9JVL9X1 345 | # +VJmdNzeOnDWYhPGsCANiU56cGGukMw+vBaZgPzpjlF5pWVySq+Jyow6U7wtveqP 346 | # 8MFtFJGFK8KADnV/4/86kvpJ3pQLP60wQ/3KhFDxi/cQuwPcn4MBe/s24huB5rSk 347 | # xac339oN4bzp8STP05N+dhgvX1UTkegmq5jrCg8ch7mZ/nk1+1EuK7EQlTDguua5 348 | # Jiick8TR5aO9ww== 349 | # SIG # End signature block 350 | -------------------------------------------------------------------------------- /IP range from cidr.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Test if an IPv4 address is within the given CIDR range 4 | 5 | .PARAMETER cidr 6 | The CIDR to test 7 | 8 | .PARAMETER address 9 | The IP address to test against the CIDR specified 10 | 11 | .EXAMPLE 12 | Test-IPRangeFromCIDR -cidr "192.168.2.1/28" -address 192.168.2.10 13 | 14 | Test if the specified IP address is contained within the given CIDR range 15 | 16 | .NOTES 17 | 18 | Modification History: 19 | 20 | 2021/11/03 @guyrleech Initial Release 21 | #> 22 | 23 | Function Test-IPRangeFromCIDR 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory=$true,HelpMessage='IP address range as CIDR')] 30 | [string]$cidr , 31 | [Parameter(Mandatory=$true,HelpMessage='IP address to check in range')] 32 | [ipaddress]$address 33 | ) 34 | 35 | [ipaddress]$startAddress = [ipaddress]::Any 36 | [ipaddress]$endAddress = [ipaddress]::Any 37 | 38 | if( Get-IPRangeFromCIDR -cidr $cidr -startAddress ([ref]$startAddress) -endAddress ([ref]$endAddress) ) 39 | { 40 | [byte[]]$bytes = $address.GetAddressBytes() 41 | [uint64]$addressToCompare = ( ( [uint64]$bytes[0] -shl 24) -bor ( [uint64]$bytes[1] -shl 16) -bor ( [uint64]$bytes[2] -shl 8) -bor [uint64]$bytes[3]) 42 | $bytes = $startAddress.GetAddressBytes() 43 | [uint64]$startAddressToCompare = ( ( [uint64]$bytes[0] -shl 24) -bor ( [uint64]$bytes[1] -shl 16) -bor ( [uint64]$bytes[2] -shl 8) -bor [uint64]$bytes[3]) 44 | $bytes = $endAddress.GetAddressBytes() 45 | [uint64]$endAddressToCompare = ( ( [uint64]$bytes[0] -shl 24) -bor ( [uint64]$bytes[1] -shl 16) -bor ( [uint64]$bytes[2] -shl 8) -bor [uint64]$bytes[3]) 46 | 47 | $addressToCompare -ge $startAddressToCompare -and $addressToCompare -le $endAddressToCompare ## return 48 | } 49 | } 50 | 51 | <# 52 | .SYNOPSIS 53 | Take a CIDR (Classless Inter-Domain Routing) notation IP v4 range and returns the first and last IPv4 addresses in the range 54 | 55 | .PARAMETER cidr 56 | The CIDR to convert 57 | 58 | .PARAMETER startAddress 59 | Will be set to the start address of the range if the CIDR is valid 60 | 61 | .PARAMETER endAddress 62 | Will be set to the end address of the range if the CIDR is valid 63 | 64 | .EXAMPLE 65 | Get-IPRangeFromCIDR -cidr "192.168.2.1/28" -Verbose -startAddress ([ref]$start) -endAddress ([ref]$end) 66 | 67 | Get the starting and ending IPv4 addresses of the specified CIDR range 68 | 69 | .NOTES 70 | Results compared with https://mxtoolbox.com/SubnetCalculator.aspx 71 | 72 | Modification History: 73 | 74 | 2021/11/03 @guyrleech Initial Release 75 | #> 76 | 77 | Function Get-IPRangeFromCIDR 78 | { 79 | [cmdletbinding()] 80 | 81 | Param 82 | ( 83 | [Parameter(Mandatory=$true,HelpMessage='IP address range as CIDR')] 84 | [string]$cidr , 85 | [Parameter(Mandatory=$true,HelpMessage='IP address range start result')] 86 | [ref]$startAddress , 87 | [Parameter(Mandatory=$true,HelpMessage='IP address range end result')] 88 | [ref]$endAddress 89 | ) 90 | 91 | [string]$ipaddressPart , [int]$bitsPart = $cidr -split '/' 92 | 93 | if( $bitsPart -eq $null -or $bitsPart -le 0 -or $bitsPart -gt 32 ) 94 | { 95 | Write-Error -Message "/$bitsPart is invalid" 96 | return $false 97 | } 98 | 99 | if( -Not ( $ipaddress = $ipaddressPart -as [ipaddress] )) 100 | { 101 | Write-Error -Message "IP address $ipaddressPart is invalid" 102 | return $false 103 | } 104 | 105 | [uint64]$mask = ([int64][System.Math]::Pow( 2 , (32 - $bitsPart) ) - 1) 106 | [byte[]]$bytes = $ipaddress.GetAddressBytes() 107 | [uint64]$octets = ( ( [uint64]$bytes[0] -shl 24) -bor ( [uint64]$bytes[1] -shl 16) -bor ( [uint64]$bytes[2] -shl 8) -bor [uint64]$bytes[3]) 108 | [uint64]$start = $octets -band ($mask -bxor 0xffffffff) 109 | [uint64]$end = $octets -bor $mask 110 | 111 | Write-Verbose -Message ('Start {0:x} end {1:x}' -f $start , $end) 112 | 113 | $startAddress.Value = [ipaddress]$start 114 | $endAddress.Value = [ipaddress]$end 115 | 116 | return $true 117 | } 118 | -------------------------------------------------------------------------------- /Leaky.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Leak working set memory. For demonstration purposes only. Do not use in a production environment. Just don't. Ever. 3 | 4 | Guy Leech, 2018 5 | #> 6 | 7 | 8 | <# 9 | .SYNOPSIS 10 | 11 | Allocate working set memory to simulate a process that leaks memory. 12 | 13 | .DESCRIPTION 14 | 15 | Run with -verbose to show progress messages 16 | 17 | .PARAMETER memoryChunk 18 | 19 | Size of the memory chunk to allocate on each loop iteration 20 | 21 | .PARAMETER frequency 22 | 23 | How often in seconds to allocate the memory chunk 24 | 25 | .PARAMETER duration 26 | 27 | How long in seconds to run for 28 | 29 | .PARAMETER waitToQuit 30 | 31 | Require key to be pressed before the script finishes 32 | 33 | .PARAMETER dontFree 34 | 35 | Do not free any of the allocated memory. The parent PowerShell process will still be consuming the leaked memory 36 | 37 | .EXAMPLE 38 | 39 | .\leaky.ps1 -memoryChunk 250MB -frequency 10 -duration 60 -verbose 40 | 41 | Allocate 250MB of extra working set every 10 seconds for 60 seconds in total, showing verbose information, and then exit, freeing the "leaked" memory first 42 | 43 | .EXAMPLE 44 | 45 | .\leaky.ps1 -memoryChunk 100MB -frequency 15 -duraton 120 -verbose -waitToQuit 46 | 47 | Allocate 100MB of extra working set every 15 seconds for 120 seconds in total, showing verbose information, and then exit, freeing the "leaked" memory first, when has been pressed 48 | 49 | .NOTES 50 | 51 | Since it is the parent powershell.exe process that is consuming the memory, if the -dontFree option is used, that PowerShell process will still be consuming the total amount of leaked working set until it is exited 52 | 53 | #> 54 | 55 | [CmdletBinding()] 56 | 57 | Param 58 | ( 59 | [Parameter(Mandatory=$true)] 60 | [long]$memoryChunk , 61 | [int]$frequency = 30 , 62 | [int]$duration = 300 , 63 | [switch]$waitToQuit , 64 | [switch]$dontFree 65 | ) 66 | 67 | Add-Type @' 68 | using System; 69 | using System.Runtime.InteropServices; 70 | 71 | namespace PInvoke.Win32 72 | { 73 | 74 | public static class Memory 75 | { 76 | [DllImport("msvcrt.dll", SetLastError=true)] 77 | public static extern IntPtr malloc( int dwBytes ); 78 | 79 | [DllImport("msvcrt.dll", SetLastError=true)] 80 | public static extern void free( IntPtr memBlock ); 81 | 82 | [DllImport("ntoskrnl.exe", SetLastError=true)] 83 | public static extern void RtlZeroMemory( IntPtr destination , int length ); 84 | } 85 | } 86 | '@ 87 | 88 | $memories = New-Object -TypeName System.Collections.ArrayList 89 | [long]$allocated = 0 90 | $timer = [Diagnostics.Stopwatch]::StartNew() 91 | 92 | Write-Verbose "$(Get-Date) : script started, working set initially $([math]::Round( (Get-Process -Id $pid).WorkingSet64 / 1MB ))MB for process $pid " 93 | 94 | While( $timer.Elapsed.TotalSeconds -le $duration ) 95 | { 96 | [long]$memory = [PInvoke.Win32.Memory]::malloc( $memoryChunk ) ; $LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error() 97 | if( $memory ) 98 | { 99 | [PInvoke.Win32.Memory]::RtlZeroMemory( $memory , $memoryChunk ) ## Need to use memory in order for it to actually get added to the working set 100 | $null = $memories.Add( $memory ) ## save the pointer lest we actually decide to free it later 101 | $allocated += $memoryChunk 102 | } 103 | else 104 | { 105 | Write-Error "$(Get-Date) : Failed to allocate $($memoryChunk / 1MB)MB - $LastError" 106 | } 107 | Write-Verbose "$(Get-Date) : total allocated $($allocated / 1MB)MB , working set now $([math]::Round( (Get-Process -Id $pid).WorkingSet64 / 1MB ))MB for process $pid - sleeping for $frequency seconds ..." 108 | 109 | Start-Sleep -Seconds $frequency 110 | } 111 | 112 | $timer.Stop() 113 | 114 | if( $waitToQuit ) 115 | { 116 | $null = Read-Host "$(Get-Date) : hit to exit " 117 | } 118 | 119 | if( ! $dontFree ) 120 | { 121 | ## We weren't really leaking! 122 | Write-Verbose "Freeing $($memories.Count) allocations of $($memoryChunk / 1MB)MB each" 123 | $memories | ForEach-Object { [PInvoke.Win32.Memory]::free( $_ ) } 124 | } 125 | 126 | Write-Verbose "$(Get-Date) : script finished, working set now $([math]::Round( (Get-Process -Id $pid).WorkingSet64 / 1MB ))MB, peak $([math]::Round( (Get-Process -Id $pid).PeakWorkingSet64 / 1MB ))MB for process $pid " 127 | -------------------------------------------------------------------------------- /Log selector.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Output log file lines where the date stamp at the start of each line is within a given time range 4 | 5 | .DESCRIPTION 6 | Log file lines need to start like this "07/01/22 11:45:00.297 " although the milliseconds value is optional as the regular expression just looks for the two space delimiters (for speed). 7 | Lines not starting with the date pattern will be output if the start date/time has been exceeded or there isn't one specified 8 | 9 | .PARAMETER logfile 10 | Path to the log file to process. If you get the error "The process cannot access the file xxx because it is being used by another process", take a copy of the file and use that instead 11 | 12 | .PARAMETER start 13 | Optional start date/time so that only lines generated on or after this will be output 14 | 15 | .PARAMETER end 16 | Optional end date/time so that only lines generated on or before this will be output 17 | 18 | .EXAMPLE 19 | & '.\Log selector.ps1' -logfile "C:\temp\broker - Copy.Log" -end "10:30" > broker.-1030.log 20 | 21 | Output all lines with a timestamp on or before 10:30 today to the file "broker.-1030.log" 22 | 23 | .EXAMPLE 24 | & '.\Log selector.ps1' -logfile "C:\temp\broker - Copy.Log" -start "10:30" > broker.1030-.log 25 | 26 | Output all lines with a timestamp on or after 10:30 today to the file "broker.1030-.log" 27 | 28 | .EXAMPLE 29 | & '.\Log selector.ps1' -logfile "C:\temp\broker - Copy.Log" -start "10:15" -end "10:30" > broker.1015-1030.log 30 | 31 | Output all lines with a timestamp between 10:15 and 10:30 today to the file "broker.1015-1030.log" 32 | 33 | .NOTES 34 | Modification History: 35 | 36 | @guyrleech 2022/07/01 Initial release 37 | @guyrleech 2022/07/01 Changed from ReadAllLines to using StreamReader for speed 38 | #> 39 | 40 | [CmdletBinding()] 41 | 42 | Param 43 | ( 44 | [Parameter(Mandatory=$true,HelpMessage='Logfile to process')] 45 | [string]$logFile , 46 | [datetime]$start , 47 | [datetime]$end 48 | ) 49 | 50 | Try 51 | { 52 | if( -Not $start -and -Not $end ) 53 | { 54 | Throw "No start or end time would output the entire file!" 55 | } 56 | $reader = $null 57 | [bool]$started = ($start -eq $null) 58 | [int]$linenumber = 0 59 | [int]$outputted = 0 60 | [datetime]$startTime = [datetime]::Now 61 | 62 | ## compile the regex for speed 63 | $regex = [regex]::new( '^([^\s]+\s[^\s]+)\s' , 'Compiled, IgnoreCase, CultureInvariant' ) 64 | 65 | $reader = $null 66 | $reader = New-Object -TypeName System.IO.StreamReader -ArgumentList $logFile 67 | if( -Not $reader ) 68 | { 69 | Throw "Failed to open $logFile" 70 | } 71 | 72 | While( -Not $reader.EndOfStream ) 73 | { 74 | $linenumber++ 75 | $line = $reader.ReadLine() 76 | 77 | ## 07/01/22 11:44:09.441 30556 0056 22484: BrokerController: 78 | ## ^^^^^^^^^^^^^^^^^^^^^ 79 | if( $line -and $line.Length -and ( $match = $regex.Matches( $line ) ) -and $match.Success -and ( $timestamp = $match.groups[0].value -as [datetime] ) ) 80 | { 81 | if( $started ) 82 | { 83 | if( -Not $end -or $timestamp -le $end ) 84 | { 85 | $outputted++ 86 | $line 87 | } 88 | else 89 | { 90 | Write-Verbose -Message "End timestamp exceeded on line $linenumber $($match.groups[0].value) in $(([datetime]::now - $startTime).TotalSeconds) seconds, outputted $outputted" 91 | exit 92 | } 93 | } 94 | else ## not found start yet 95 | { 96 | if( $timestamp -ge $start ) 97 | { 98 | Write-Verbose -Message "Start timestamp found on line $linenumber $($match.groups[0].value) in $(([datetime]::now - $startTime).TotalSeconds) seconds" 99 | $started = $true 100 | $outputted++ 101 | $line 102 | } 103 | } 104 | } 105 | elseif( $started ) ## could be a continuation, stack trace, etc 106 | { 107 | $outputted++ 108 | $line 109 | } 110 | } 111 | } 112 | Catch 113 | { 114 | Throw $_ 115 | } 116 | Finally 117 | { 118 | Write-Verbose -Message "Exiting. Read $linenumber lines in $(([datetime]::now - $startTime).TotalSeconds) seconds, outputted $outputted" 119 | 120 | if( $reader -and $outputted -eq 0 ) 121 | { 122 | Write-Warning -Message "No lines in time range found" 123 | } 124 | if( $reader ) 125 | { 126 | $reader.Close() 127 | $reader.Dispose() 128 | $reader = $null 129 | } 130 | } 131 | 132 | # SIG # Begin signature block 133 | # MIIZsAYJKoZIhvcNAQcCoIIZoTCCGZ0CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB 134 | # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR 135 | # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQULtc9Iuj0h6P6DcQmspgGq9wJ 136 | # a2ugghS+MIIE/jCCA+agAwIBAgIQDUJK4L46iP9gQCHOFADw3TANBgkqhkiG9w0B 137 | # AQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD 138 | # VQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFz 139 | # c3VyZWQgSUQgVGltZXN0YW1waW5nIENBMB4XDTIxMDEwMTAwMDAwMFoXDTMxMDEw 140 | # NjAwMDAwMFowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu 141 | # MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMTCCASIwDQYJKoZIhvcN 142 | # AQEBBQADggEPADCCAQoCggEBAMLmYYRnxYr1DQikRcpja1HXOhFCvQp1dU2UtAxQ 143 | # tSYQ/h3Ib5FrDJbnGlxI70Tlv5thzRWRYlq4/2cLnGP9NmqB+in43Stwhd4CGPN4 144 | # bbx9+cdtCT2+anaH6Yq9+IRdHnbJ5MZ2djpT0dHTWjaPxqPhLxs6t2HWc+xObTOK 145 | # fF1FLUuxUOZBOjdWhtyTI433UCXoZObd048vV7WHIOsOjizVI9r0TXhG4wODMSlK 146 | # XAwxikqMiMX3MFr5FK8VX2xDSQn9JiNT9o1j6BqrW7EdMMKbaYK02/xWVLwfoYer 147 | # vnpbCiAvSwnJlaeNsvrWY4tOpXIc7p96AXP4Gdb+DUmEvQECAwEAAaOCAbgwggG0 148 | # MA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsG 149 | # AQUFBwMIMEEGA1UdIAQ6MDgwNgYJYIZIAYb9bAcBMCkwJwYIKwYBBQUHAgEWG2h0 150 | # dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAfBgNVHSMEGDAWgBT0tuEgHf4prtLk 151 | # YaWyoiWyyBc1bjAdBgNVHQ4EFgQUNkSGjqS6sGa+vCgtHUQ23eNqerwwcQYDVR0f 152 | # BGowaDAyoDCgLoYsaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJl 153 | # ZC10cy5jcmwwMqAwoC6GLGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFz 154 | # c3VyZWQtdHMuY3JsMIGFBggrBgEFBQcBAQR5MHcwJAYIKwYBBQUHMAGGGGh0dHA6 155 | # Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBPBggrBgEFBQcwAoZDaHR0cDovL2NhY2VydHMu 156 | # ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkFzc3VyZWRJRFRpbWVzdGFtcGluZ0NB 157 | # LmNydDANBgkqhkiG9w0BAQsFAAOCAQEASBzctemaI7znGucgDo5nRv1CclF0CiNH 158 | # o6uS0iXEcFm+FKDlJ4GlTRQVGQd58NEEw4bZO73+RAJmTe1ppA/2uHDPYuj1UUp4 159 | # eTZ6J7fz51Kfk6ftQ55757TdQSKJ+4eiRgNO/PT+t2R3Y18jUmmDgvoaU+2QzI2h 160 | # F3MN9PNlOXBL85zWenvaDLw9MtAby/Vh/HUIAHa8gQ74wOFcz8QRcucbZEnYIpp1 161 | # FUL1LTI4gdr0YKK6tFL7XOBhJCVPst/JKahzQ1HavWPWH1ub9y4bTxMd90oNcX6X 162 | # t/Q/hOvB46NJofrOp79Wz7pZdmGJX36ntI5nePk2mOHLKNpbh6aKLzCCBTAwggQY 163 | # oAMCAQICEAQJGBtf1btmdVNDtW+VUAgwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UE 164 | # BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj 165 | # ZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4X 166 | # DTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcjELMAkGA1UEBhMCVVMxFTAT 167 | # BgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEx 168 | # MC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2lnbmluZyBD 169 | # QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPjTsxx/DhGvZ3cH0wsx 170 | # SRnP0PtFmbE620T1f+Wondsy13Hqdp0FLreP+pJDwKX5idQ3Gde2qvCchqXYJawO 171 | # eSg6funRZ9PG+yknx9N7I5TkkSOWkHeC+aGEI2YSVDNQdLEoJrskacLCUvIUZ4qJ 172 | # RdQtoaPpiCwgla4cSocI3wz14k1gGL6qxLKucDFmM3E+rHCiq85/6XzLkqHlOzEc 173 | # z+ryCuRXu0q16XTmK/5sy350OTYNkO/ktU6kqepqCquE86xnTrXE94zRICUj6whk 174 | # PlKWwfIPEvTFjg/BougsUfdzvL2FsWKDc0GCB+Q4i2pzINAPZHM8np+mM6n9Gd8l 175 | # k9ECAwEAAaOCAc0wggHJMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQD 176 | # AgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHkGCCsGAQUFBwEBBG0wazAkBggrBgEF 177 | # BQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAChjdodHRw 178 | # Oi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0Eu 179 | # Y3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20v 180 | # RGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRwOi8vY3JsMy5k 181 | # aWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsME8GA1UdIARI 182 | # MEYwOAYKYIZIAYb9bAACBDAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdp 183 | # Y2VydC5jb20vQ1BTMAoGCGCGSAGG/WwDMB0GA1UdDgQWBBRaxLl7KgqjpepxA8Bg 184 | # +S32ZXUOWDAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkqhkiG 185 | # 9w0BAQsFAAOCAQEAPuwNWiSz8yLRFcgsfCUpdqgdXRwtOhrE7zBh134LYP3DPQ/E 186 | # r4v97yrfIFU3sOH20ZJ1D1G0bqWOWuJeJIFOEKTuP3GOYw4TS63XX0R58zYUBor3 187 | # nEZOXP+QsRsHDpEV+7qvtVHCjSSuJMbHJyqhKSgaOnEoAjwukaPAJRHinBRHoXpo 188 | # aK+bp1wgXNlxsQyPu6j4xRJon89Ay0BEpRPw5mQMJQhCMrI2iiQC/i9yfhzXSUWW 189 | # 6Fkd6fp0ZGuy62ZD2rOwjNXpDd32ASDOmTFjPQgaGLOBm0/GkxAG/AeB+ova+YJJ 190 | # 92JuoVP6EpQYhS6SkepobEQysmah5xikmmRR7zCCBTEwggQZoAMCAQICEAqhJdbW 191 | # Mht+QeQF2jaXwhUwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCVVMxFTATBgNV 192 | # BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIG 193 | # A1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTE2MDEwNzEyMDAw 194 | # MFoXDTMxMDEwNzEyMDAwMFowcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lD 195 | # ZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGln 196 | # aUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQTCCASIwDQYJKoZI 197 | # hvcNAQEBBQADggEPADCCAQoCggEBAL3QMu5LzY9/3am6gpnFOVQoV7YjSsQOB0Uz 198 | # URB90Pl9TWh+57ag9I2ziOSXv2MhkJi/E7xX08PhfgjWahQAOPcuHjvuzKb2Mln+ 199 | # X2U/4Jvr40ZHBhpVfgsnfsCi9aDg3iI/Dv9+lfvzo7oiPhisEeTwmQNtO4V8CdPu 200 | # XciaC1TjqAlxa+DPIhAPdc9xck4Krd9AOly3UeGheRTGTSQjMF287DxgaqwvB8z9 201 | # 8OpH2YhQXv1mblZhJymJhFHmgudGUP2UKiyn5HU+upgPhH+fMRTWrdXyZMt7HgXQ 202 | # hBlyF/EXBu89zdZN7wZC/aJTKk+FHcQdPK/P2qwQ9d2srOlW/5MCAwEAAaOCAc4w 203 | # ggHKMB0GA1UdDgQWBBT0tuEgHf4prtLkYaWyoiWyyBc1bjAfBgNVHSMEGDAWgBRF 204 | # 66Kv9JLLgjEtUYunpyGd823IDzASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB 205 | # /wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDCDB5BggrBgEFBQcBAQRtMGswJAYI 206 | # KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3 207 | # aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9v 208 | # dENBLmNydDCBgQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQu 209 | # Y29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2Ny 210 | # bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBQBgNV 211 | # HSAESTBHMDgGCmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cu 212 | # ZGlnaWNlcnQuY29tL0NQUzALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggEB 213 | # AHGVEulRh1Zpze/d2nyqY3qzeM8GN0CE70uEv8rPAwL9xafDDiBCLK938ysfDCFa 214 | # KrcFNB1qrpn4J6JmvwmqYN92pDqTD/iy0dh8GWLoXoIlHsS6HHssIeLWWywUNUME 215 | # aLLbdQLgcseY1jxk5R9IEBhfiThhTWJGJIdjjJFSLK8pieV4H9YLFKWA1xJHcLN1 216 | # 1ZOFk362kmf7U2GJqPVrlsD0WGkNfMgBsbkodbeZY4UijGHKeZR+WfyMD+NvtQEm 217 | # tmyl7odRIeRYYJu6DC0rbaLEfrvEJStHAgh8Sa4TtuF8QkIoxhhWz0E0tmZdtnR7 218 | # 9VYzIi8iNrJLokqV2PWmjlIwggVPMIIEN6ADAgECAhAE/eOq2921q55B9NnVIXVO 219 | # MA0GCSqGSIb3DQEBCwUAMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy 220 | # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lD 221 | # ZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwHhcNMjAwNzIwMDAw 222 | # MDAwWhcNMjMwNzI1MTIwMDAwWjCBizELMAkGA1UEBhMCR0IxEjAQBgNVBAcTCVdh 223 | # a2VmaWVsZDEmMCQGA1UEChMdU2VjdXJlIFBsYXRmb3JtIFNvbHV0aW9ucyBMdGQx 224 | # GDAWBgNVBAsTD1NjcmlwdGluZ0hlYXZlbjEmMCQGA1UEAxMdU2VjdXJlIFBsYXRm 225 | # b3JtIFNvbHV0aW9ucyBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB 226 | # AQCvbSdd1oAAu9rTtdnKSlGWKPF8g+RNRAUDFCBdNbYbklzVhB8hiMh48LqhoP7d 227 | # lzZY3YmuxztuPlB7k2PhAccd/eOikvKDyNeXsSa3WaXLNSu3KChDVekEFee/vR29 228 | # mJuujp1eYrz8zfvDmkQCP/r34Bgzsg4XPYKtMitCO/CMQtI6Rnaj7P6Kp9rH1nVO 229 | # /zb7KD2IMedTFlaFqIReT0EVG/1ZizOpNdBMSG/x+ZQjZplfjyyjiYmE0a7tWnVM 230 | # Z4KKTUb3n1CTuwWHfK9G6CNjQghcFe4D4tFPTTKOSAx7xegN1oGgifnLdmtDtsJU 231 | # OOhOtyf9Kp8e+EQQyPVrV/TNAgMBAAGjggHFMIIBwTAfBgNVHSMEGDAWgBRaxLl7 232 | # KgqjpepxA8Bg+S32ZXUOWDAdBgNVHQ4EFgQUTXqi+WoiTm5fYlDLqiDQ4I+uyckw 233 | # DgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHcGA1UdHwRwMG4w 234 | # NaAzoDGGL2h0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtY3Mt 235 | # ZzEuY3JsMDWgM6Axhi9odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1hc3N1 236 | # cmVkLWNzLWcxLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwDATAqMCgGCCsGAQUF 237 | # BwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEEATCBhAYI 238 | # KwYBBQUHAQEEeDB2MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j 239 | # b20wTgYIKwYBBQUHMAKGQmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp 240 | # Q2VydFNIQTJBc3N1cmVkSURDb2RlU2lnbmluZ0NBLmNydDAMBgNVHRMBAf8EAjAA 241 | # MA0GCSqGSIb3DQEBCwUAA4IBAQBT3M71SlOQ8vwM2txshp/XDvfoKBYHkpFCyanW 242 | # aFdsYQJQIKk4LOVgUJJ6LAf0xPSN7dZpjFaoilQy8Ajyd0U9UOnlEX4gk2J+z5i4 243 | # sFxK/W2KU1j6R9rY5LbScWtsV+X1BtHihpzPywGGE5eth5Q5TixMdI9CN3eWnKGF 244 | # kY13cI69zZyyTnkkb+HaFHZ8r6binvOyzMr69+oRf0Bv/uBgyBKjrmGEUxJZy+00 245 | # 7fbmYDEclgnWT1cRROarzbxmZ8R7Iyor0WU3nKRgkxan+8rzDhzpZdtgIFdYvjeO 246 | # c/IpPi2mI6NY4jqDXwkx1TEIbjUdrCmEfjhAfMTU094L7VSNMYIEXDCCBFgCAQEw 247 | # gYYwcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UE 248 | # CxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1 249 | # cmVkIElEIENvZGUgU2lnbmluZyBDQQIQBP3jqtvdtaueQfTZ1SF1TjAJBgUrDgMC 250 | # GgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYK 251 | # KwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG 252 | # 9w0BCQQxFgQU1iynj1rzDNELKitpnFyyji44kJkwDQYJKoZIhvcNAQEBBQAEggEA 253 | # DLMN04nZU72+DvZz8tCndaMWcSoMMpgW+Y/KGX6KEq8uT3a9WFnViDw1G3Rl6XH2 254 | # JbzNphntcfZXVwVzaJmEq7gnUw/wXC0qQDLEHjH2jVh0soJ8+jAfh5pYwSTq1apK 255 | # igjB51MpeO5056h3Z37cyzm7PkXAKbkW3j0vWgahLIT9q+2wqCxR54BiM0qclnjp 256 | # k6Jy2hMmsqlCihYKj4nXZ7ziopw4ptpVPIZ+kSRtsMHEv55HNutjWujmNPQNOV+s 257 | # vYtSqTfx/Wq+yJlgZxkj+WTvZUCH2vEzOkKzcWlD9E9kVp6lM8sc3GHBqObHi1G0 258 | # W1wVa94+MhbDDEIopGdrsaGCAjAwggIsBgkqhkiG9w0BCQYxggIdMIICGQIBATCB 259 | # hjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL 260 | # ExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3Vy 261 | # ZWQgSUQgVGltZXN0YW1waW5nIENBAhANQkrgvjqI/2BAIc4UAPDdMA0GCWCGSAFl 262 | # AwQCAQUAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUx 263 | # DxcNMjIwMTA3MTYwNjQ4WjAvBgkqhkiG9w0BCQQxIgQgPpmwqtqn4YNfEirpkE7N 264 | # fXqdl8OHtjq3R+iZ42yV3R4wDQYJKoZIhvcNAQEBBQAEggEAqzQbgBxZ5w+88yU4 265 | # vPglKR2inXj1GVgynzzrCleNM2KjGkBY+gjSwSZY3FfiJz32RwJ8rMmu1ZfpYBOJ 266 | # I4Dy2TpWnKfKGq6A5WMzskIH9ekEXfxUUULoQ4Ar/nVZAhKOqwW4Pgd3b7UtlF0b 267 | # D/M2ZHf5Mu9k7hUdf4xSCl2NKpH08Y4Uxd631GtRHvJIlhOHKDuwNYQ1bWq6AmtW 268 | # 734Ocw31uyvsW9iamW6Q87FCDY3ZPE6hh5K+GbWnel3kXZ71MoWLW9rEPr3/8Vg2 269 | # PeFrrpFTGVtK2ovDISMSnlubcoC+T0XE1RXTL6Jdsp3a5mWsEsKBXi/RMmc9leXI 270 | # 7IqvKQ== 271 | # SIG # End signature block 272 | -------------------------------------------------------------------------------- /Parse smbstatus.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 3 2 | 3 | <# 4 | .SYNOPSIS 5 | Run smbstatus to get file locks from Synology NAS and parse the result into objects 6 | 7 | .DESCRIPTION 8 | Help to find what user and machine files are locked from, e.g. FSlogix profile disks 9 | 10 | .PARAMETER connection 11 | The username and host to connect to 12 | 13 | .PARAMETER resolve 14 | Resolve IP addresses to host names/aliases 15 | 16 | .PARAMETER port 17 | The SSH port to connect to 18 | 19 | .PARAMETER sshExe 20 | The ssh command to use - default is ssh.exe which must be in %PATH% 21 | 22 | .PARAMETER sshOptions 23 | Options to pass to ssh.exe 24 | 25 | .PARAMETER command 26 | The command to run 27 | 28 | .PARAMETER dividerPattern 29 | Regex for delimiter between different sections of output 30 | 31 | .EXAMPLE 32 | & '.\Parse smbstatus.ps1' -connection root@grl-nas02 -resolve 33 | 34 | Connect to the Synology device grl-nas02 as user root, run the smbstatus command and parse the output into objects, including attempting to resolve IP addresses to hostnames & aliases 35 | 36 | .NOTES 37 | For passwordless ssh connections, setup keys - https://kb.synology.com/en-uk/DSM/tutorial/How_to_log_in_to_DSM_with_key_pairs_as_admin_or_root_permission_via_SSH_on_computers - but protect/secure those keys 38 | 39 | smbstatus must be run as root 40 | 41 | Modification History: 42 | 43 | @guyrleech 2022/01/16 Initial version 44 | @guyrleech 2022/01/24 Option to resolve IP address to hostname. Added check for ssh.exe 45 | @guyrleech 2022/04/01 Fixed date parsing regex issue 46 | #> 47 | 48 | <# 49 | Copyright © 2022 Guy Leech 50 | 51 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, 52 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 53 | 54 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 55 | 56 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 57 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 58 | #> 59 | 60 | [CmdletBinding()] 61 | 62 | Param 63 | ( 64 | [Parameter(Mandatory=$true,HelpMessage='SSH host')] 65 | [string]$connection , 66 | [switch]$resolve, 67 | [int]$port , 68 | [string]$sshExe = 'ssh.exe' , 69 | [string]$sshOptions = '-o ConnectTimeout=15 -o batchmode=yes' , 70 | [string]$command = 'smbstatus' , 71 | [string]$dividerPattern = '^Locked files:' 72 | ) 73 | 74 | [string[]]$fieldHeaders = @( 75 | 'Pid' , 76 | 'Id' , 77 | 'user' , 78 | 'Address' , 79 | 'Share' , 80 | 'Version' 81 | ) 82 | 83 | [string]$portparameter = $null 84 | 85 | if( $port -gt 0 ) 86 | { 87 | $portparameter = "-p $port" 88 | } 89 | 90 | ## first half is shares (without headers), second half is locked files 91 | <# 92 | 30706 1642246968 GUYRLEECH\admingle 192.168.0.133 homes SMB3_11 93 | 30706 1642247068 GUYRLEECH\admingle 192.168.0.133 video SMB3_11 94 | 29841 1642257276 GUYRLEECH\admingle 192.168.0.197 homes SMB3_11 95 | 11271 1642246118 GUYRLEECH\admingle 10.1.1.148 Software SMB3_11 96 | 11271 1642246117 GUYRLEECH\admingle 10.1.1.148 homes SMB3_11 97 | 19321 1642230239 GUYRLEECH\billybob 10.1.1.151 Software SMB3_11 98 | 30706 1642237811 GUYRLEECH\admingle 192.168.0.133 Software SMB3_11 99 | 29841 1642257815 GUYRLEECH\admingle 192.168.0.197 Software SMB3_11 100 | 101 | Locked files: 102 | Pid Uid DenyMode Access R/W Oplock SharePath Name Time 103 | -------------------------------------------------------------------------------------------------- 104 | 30706 0 DENY_NONE 0x100080 RDONLY NONE /volume1/homes . Sat Jan 15 11:45:03 2022 105 | 19321 671089749 DENY_NONE 0x100081 RDONLY NONE /volume1/Software . Sat Jan 15 07:03:59 2022 106 | 11271 0 DENY_NONE 0x100081 RDONLY NONE /volume1/Software . Sat Jan 15 11:28:37 2022 107 | 30706 0 DENY_NONE 0x100080 RDONLY NONE /volume1/Software . Sat Jan 15 11:56:00 2022 108 | 19321 671089749 DENY_NONE 0x120089 RDONLY LEASE(R) /volume1/Software FSLogix/S-1-5-21-1721611859-3364803896-2099701507-1109_BillyBob/Profile_BillyBob.VHDX Sat Jan 15 07:03:59 2022 109 | 19321 671089749 DENY_WRITE 0x12019f RDWR LEASE(R) /volume1/Software FSLogix/S-1-5-21-1721611859-3364803896-2099701507-1109_BillyBob/Profile_BillyBob.VHDX Sat Jan 15 07:03:59 2022 110 | #> 111 | 112 | if( -Not ( Get-Command -Name $sshExe -CommandType Application -ErrorAction SilentlyContinue ) ) 113 | { 114 | Throw "Unable to locate $sshExe - try installing by running `"Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0`"" 115 | } 116 | 117 | [bool]$gotLockedFilesLine = $false 118 | $headers = New-Object -TypeName System.Collections.Generic.List[string] 119 | $pids = New-Object -TypeName System.Collections.Generic.List[object] 120 | 121 | ## found that using & to invoke the command put quotes around some arguments which breaks ssh.exe 122 | Invoke-Expression -Command "$sshExe $sshoptions $connection $portparameter $command" | ForEach-Object ` 123 | { 124 | [string]$line = $_.Trim() 125 | if( $line.Length ) 126 | { 127 | if( $gotLockedFilesLine ) 128 | { 129 | if( $headers.Count ) 130 | { 131 | ## fields are delimited by two or more spaces but need to be able to deal with file names with 2 or more consecutive spaces 132 | ## 19321 671089749 DENY_NONE 0x120089 RDONLY LEASE(R) /volume1/Software FSLogix/S-1-5-21-1721611859-3364803896-2099701507-1109_BillyBob/Profile_BillyBob.VHDX Sat Jan 15 07:03:59 2022 133 | [string[]]$lineparts = $line -split '\s+' 134 | Write-Verbose -Message $line 135 | if( $lineparts -and $lineparts.Count -ge $headers.Count ) 136 | { 137 | [hashtable]$item = @{} 138 | $time = $null 139 | [string]$regex = $null 140 | For( [int]$index = 0 ; $index -lt $headers.Count ; $index++ ) 141 | { 142 | $property = $lineparts[ $index ] 143 | if( $headers[ $index ] -eq 'pid' ) 144 | { 145 | if( $pidentry = $pids.Where( { $_.pid -eq $property } , 1 ) ) ## could be multiple entries 146 | { 147 | $item.Add( 'User' , $pidentry.User ) 148 | $item.Add( 'Address' , $pidentry.Address ) 149 | if( $pidentry.Address -and $resolve ) 150 | { 151 | $hostname = $null 152 | if( $hostDetails = [System.Net.Dns]::GetHostByAddress( [ipaddress]$pidentry.Address ) ) 153 | { 154 | $hostname = $hostDetails.HostName 155 | if( $hostDetails.Aliases -and $hostDetails.Aliases.Count ) 156 | { 157 | $hostname = -join ( $hostname , " " , ($hostDetails.Aliases -join ',' )) 158 | } 159 | } 160 | 161 | $item.Add( 'Hostname' , $hostname ) 162 | } 163 | } 164 | else 165 | { 166 | Write-Warning -Message "No entry found for pid $property on line : $line" 167 | } 168 | } 169 | elseif( $headers[ $index ] -eq 'name' ) 170 | { 171 | ## file name could have spaces so we grab the date off the end of the line which allows us to then work out where the file name is 172 | if( $line -match ' (\w{3} \w{3}\s+\d*\s+\d{2}:\d{2}:\d{2} \d{4})$' ) 173 | { 174 | $time = New-Object -TypeName datetime 175 | if( [datetime]::TryParseExact( $matches[1] , 'ddd MMM d HH:mm:ss yyyy' , [System.Globalization.CultureInfo]::InvariantCulture , [System.Globalization.DateTimeStyles]::AllowWhiteSpaces , [ref]$time ) ) 176 | { 177 | $item.Add( 'Time' , $time ) 178 | ## we have been building $regex to match the line before the file name so we can match the line up to this and calculate where the file name starts 179 | [int]$datematchLength = $matches[0].Length 180 | if( $line -match $regex ) 181 | { 182 | [int]$filenameLength = $line.Length - $matches[0].Length - $datematchLength - 1 183 | if( $filenameLength -gt 0 ) 184 | { 185 | $property = $line.Substring( $matches[0].Length , $filenameLength ) 186 | } 187 | else 188 | { 189 | $property = $null 190 | } 191 | } 192 | else 193 | { 194 | Write-Warning -Message "Failed to match regex `"$regex`" in line $line" 195 | } 196 | ## else no space so file name is ok as is 197 | } 198 | else 199 | { 200 | Write-Warning -Message "Failed to parse date/time $property" 201 | } 202 | } 203 | else 204 | { 205 | Write-Warning -Message "Failed to parse date from end of line : $line" 206 | } 207 | } 208 | $item.Add( $headers[ $index ] , $property ) 209 | if( $time ) 210 | { 211 | break ## name is followed only by date which we have parsed 212 | } 213 | $regex = -join ( $regex , [regex]::Escape( $property ) , '\s+' ) 214 | } 215 | [pscustomobject]$item 216 | } 217 | elseif( $line -notmatch '^----' ) 218 | { 219 | Write-Warning -Message "Failed to parse : $line" 220 | } 221 | } 222 | else ## need to read the headers 223 | { 224 | $headers = @( $line -split '\s+' ) 225 | } 226 | } 227 | elseif( $line -match $dividerPattern ) 228 | { 229 | $gotLockedFilesLine = $true 230 | } 231 | else ## first part of the output is tab delimited, without headers 232 | { 233 | if( ( $fields = $line -split '\s+' ) -and $fields.Count ) 234 | { 235 | if( $fields.Count -eq $fieldHeaders.Count ) 236 | { 237 | [hashtable]$item = @{} 238 | For( [int]$index = 0 ; $index -lt $fieldHeaders.Count ; $index++ ) 239 | { 240 | $item.Add( $fieldHeaders[ $index ] , $fields[ $index ] ) 241 | } 242 | $pids.Add( [pscustomobject]$item ) 243 | } 244 | else 245 | { 246 | Write-Warning -Message "Got $($fields.Count) fields, not $($fieldHeaders.Count) : $line" 247 | } 248 | } 249 | else 250 | { 251 | Write-Warning -Message "Unable to process line : $line" 252 | } 253 | } 254 | } 255 | } 256 | 257 | # SIG # Begin signature block 258 | # MIIZsAYJKoZIhvcNAQcCoIIZoTCCGZ0CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB 259 | # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR 260 | # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUfJI/FkmCWZmTOvB7pEC3wH1l 261 | # H9WgghS+MIIE/jCCA+agAwIBAgIQDUJK4L46iP9gQCHOFADw3TANBgkqhkiG9w0B 262 | # AQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD 263 | # VQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFz 264 | # c3VyZWQgSUQgVGltZXN0YW1waW5nIENBMB4XDTIxMDEwMTAwMDAwMFoXDTMxMDEw 265 | # NjAwMDAwMFowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu 266 | # MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMTCCASIwDQYJKoZIhvcN 267 | # AQEBBQADggEPADCCAQoCggEBAMLmYYRnxYr1DQikRcpja1HXOhFCvQp1dU2UtAxQ 268 | # tSYQ/h3Ib5FrDJbnGlxI70Tlv5thzRWRYlq4/2cLnGP9NmqB+in43Stwhd4CGPN4 269 | # bbx9+cdtCT2+anaH6Yq9+IRdHnbJ5MZ2djpT0dHTWjaPxqPhLxs6t2HWc+xObTOK 270 | # fF1FLUuxUOZBOjdWhtyTI433UCXoZObd048vV7WHIOsOjizVI9r0TXhG4wODMSlK 271 | # XAwxikqMiMX3MFr5FK8VX2xDSQn9JiNT9o1j6BqrW7EdMMKbaYK02/xWVLwfoYer 272 | # vnpbCiAvSwnJlaeNsvrWY4tOpXIc7p96AXP4Gdb+DUmEvQECAwEAAaOCAbgwggG0 273 | # MA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsG 274 | # AQUFBwMIMEEGA1UdIAQ6MDgwNgYJYIZIAYb9bAcBMCkwJwYIKwYBBQUHAgEWG2h0 275 | # dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAfBgNVHSMEGDAWgBT0tuEgHf4prtLk 276 | # YaWyoiWyyBc1bjAdBgNVHQ4EFgQUNkSGjqS6sGa+vCgtHUQ23eNqerwwcQYDVR0f 277 | # BGowaDAyoDCgLoYsaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJl 278 | # ZC10cy5jcmwwMqAwoC6GLGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFz 279 | # c3VyZWQtdHMuY3JsMIGFBggrBgEFBQcBAQR5MHcwJAYIKwYBBQUHMAGGGGh0dHA6 280 | # Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBPBggrBgEFBQcwAoZDaHR0cDovL2NhY2VydHMu 281 | # ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkFzc3VyZWRJRFRpbWVzdGFtcGluZ0NB 282 | # LmNydDANBgkqhkiG9w0BAQsFAAOCAQEASBzctemaI7znGucgDo5nRv1CclF0CiNH 283 | # o6uS0iXEcFm+FKDlJ4GlTRQVGQd58NEEw4bZO73+RAJmTe1ppA/2uHDPYuj1UUp4 284 | # eTZ6J7fz51Kfk6ftQ55757TdQSKJ+4eiRgNO/PT+t2R3Y18jUmmDgvoaU+2QzI2h 285 | # F3MN9PNlOXBL85zWenvaDLw9MtAby/Vh/HUIAHa8gQ74wOFcz8QRcucbZEnYIpp1 286 | # FUL1LTI4gdr0YKK6tFL7XOBhJCVPst/JKahzQ1HavWPWH1ub9y4bTxMd90oNcX6X 287 | # t/Q/hOvB46NJofrOp79Wz7pZdmGJX36ntI5nePk2mOHLKNpbh6aKLzCCBTAwggQY 288 | # oAMCAQICEAQJGBtf1btmdVNDtW+VUAgwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UE 289 | # BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj 290 | # ZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4X 291 | # DTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcjELMAkGA1UEBhMCVVMxFTAT 292 | # BgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEx 293 | # MC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2lnbmluZyBD 294 | # QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPjTsxx/DhGvZ3cH0wsx 295 | # SRnP0PtFmbE620T1f+Wondsy13Hqdp0FLreP+pJDwKX5idQ3Gde2qvCchqXYJawO 296 | # eSg6funRZ9PG+yknx9N7I5TkkSOWkHeC+aGEI2YSVDNQdLEoJrskacLCUvIUZ4qJ 297 | # RdQtoaPpiCwgla4cSocI3wz14k1gGL6qxLKucDFmM3E+rHCiq85/6XzLkqHlOzEc 298 | # z+ryCuRXu0q16XTmK/5sy350OTYNkO/ktU6kqepqCquE86xnTrXE94zRICUj6whk 299 | # PlKWwfIPEvTFjg/BougsUfdzvL2FsWKDc0GCB+Q4i2pzINAPZHM8np+mM6n9Gd8l 300 | # k9ECAwEAAaOCAc0wggHJMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQD 301 | # AgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHkGCCsGAQUFBwEBBG0wazAkBggrBgEF 302 | # BQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAChjdodHRw 303 | # Oi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0Eu 304 | # Y3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20v 305 | # RGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRwOi8vY3JsMy5k 306 | # aWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsME8GA1UdIARI 307 | # MEYwOAYKYIZIAYb9bAACBDAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdp 308 | # Y2VydC5jb20vQ1BTMAoGCGCGSAGG/WwDMB0GA1UdDgQWBBRaxLl7KgqjpepxA8Bg 309 | # +S32ZXUOWDAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkqhkiG 310 | # 9w0BAQsFAAOCAQEAPuwNWiSz8yLRFcgsfCUpdqgdXRwtOhrE7zBh134LYP3DPQ/E 311 | # r4v97yrfIFU3sOH20ZJ1D1G0bqWOWuJeJIFOEKTuP3GOYw4TS63XX0R58zYUBor3 312 | # nEZOXP+QsRsHDpEV+7qvtVHCjSSuJMbHJyqhKSgaOnEoAjwukaPAJRHinBRHoXpo 313 | # aK+bp1wgXNlxsQyPu6j4xRJon89Ay0BEpRPw5mQMJQhCMrI2iiQC/i9yfhzXSUWW 314 | # 6Fkd6fp0ZGuy62ZD2rOwjNXpDd32ASDOmTFjPQgaGLOBm0/GkxAG/AeB+ova+YJJ 315 | # 92JuoVP6EpQYhS6SkepobEQysmah5xikmmRR7zCCBTEwggQZoAMCAQICEAqhJdbW 316 | # Mht+QeQF2jaXwhUwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCVVMxFTATBgNV 317 | # BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIG 318 | # A1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTE2MDEwNzEyMDAw 319 | # MFoXDTMxMDEwNzEyMDAwMFowcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lD 320 | # ZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGln 321 | # aUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQTCCASIwDQYJKoZI 322 | # hvcNAQEBBQADggEPADCCAQoCggEBAL3QMu5LzY9/3am6gpnFOVQoV7YjSsQOB0Uz 323 | # URB90Pl9TWh+57ag9I2ziOSXv2MhkJi/E7xX08PhfgjWahQAOPcuHjvuzKb2Mln+ 324 | # X2U/4Jvr40ZHBhpVfgsnfsCi9aDg3iI/Dv9+lfvzo7oiPhisEeTwmQNtO4V8CdPu 325 | # XciaC1TjqAlxa+DPIhAPdc9xck4Krd9AOly3UeGheRTGTSQjMF287DxgaqwvB8z9 326 | # 8OpH2YhQXv1mblZhJymJhFHmgudGUP2UKiyn5HU+upgPhH+fMRTWrdXyZMt7HgXQ 327 | # hBlyF/EXBu89zdZN7wZC/aJTKk+FHcQdPK/P2qwQ9d2srOlW/5MCAwEAAaOCAc4w 328 | # ggHKMB0GA1UdDgQWBBT0tuEgHf4prtLkYaWyoiWyyBc1bjAfBgNVHSMEGDAWgBRF 329 | # 66Kv9JLLgjEtUYunpyGd823IDzASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB 330 | # /wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDCDB5BggrBgEFBQcBAQRtMGswJAYI 331 | # KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3 332 | # aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9v 333 | # dENBLmNydDCBgQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQu 334 | # Y29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2Ny 335 | # bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBQBgNV 336 | # HSAESTBHMDgGCmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cu 337 | # ZGlnaWNlcnQuY29tL0NQUzALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggEB 338 | # AHGVEulRh1Zpze/d2nyqY3qzeM8GN0CE70uEv8rPAwL9xafDDiBCLK938ysfDCFa 339 | # KrcFNB1qrpn4J6JmvwmqYN92pDqTD/iy0dh8GWLoXoIlHsS6HHssIeLWWywUNUME 340 | # aLLbdQLgcseY1jxk5R9IEBhfiThhTWJGJIdjjJFSLK8pieV4H9YLFKWA1xJHcLN1 341 | # 1ZOFk362kmf7U2GJqPVrlsD0WGkNfMgBsbkodbeZY4UijGHKeZR+WfyMD+NvtQEm 342 | # tmyl7odRIeRYYJu6DC0rbaLEfrvEJStHAgh8Sa4TtuF8QkIoxhhWz0E0tmZdtnR7 343 | # 9VYzIi8iNrJLokqV2PWmjlIwggVPMIIEN6ADAgECAhAE/eOq2921q55B9NnVIXVO 344 | # MA0GCSqGSIb3DQEBCwUAMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy 345 | # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lD 346 | # ZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwHhcNMjAwNzIwMDAw 347 | # MDAwWhcNMjMwNzI1MTIwMDAwWjCBizELMAkGA1UEBhMCR0IxEjAQBgNVBAcTCVdh 348 | # a2VmaWVsZDEmMCQGA1UEChMdU2VjdXJlIFBsYXRmb3JtIFNvbHV0aW9ucyBMdGQx 349 | # GDAWBgNVBAsTD1NjcmlwdGluZ0hlYXZlbjEmMCQGA1UEAxMdU2VjdXJlIFBsYXRm 350 | # b3JtIFNvbHV0aW9ucyBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB 351 | # AQCvbSdd1oAAu9rTtdnKSlGWKPF8g+RNRAUDFCBdNbYbklzVhB8hiMh48LqhoP7d 352 | # lzZY3YmuxztuPlB7k2PhAccd/eOikvKDyNeXsSa3WaXLNSu3KChDVekEFee/vR29 353 | # mJuujp1eYrz8zfvDmkQCP/r34Bgzsg4XPYKtMitCO/CMQtI6Rnaj7P6Kp9rH1nVO 354 | # /zb7KD2IMedTFlaFqIReT0EVG/1ZizOpNdBMSG/x+ZQjZplfjyyjiYmE0a7tWnVM 355 | # Z4KKTUb3n1CTuwWHfK9G6CNjQghcFe4D4tFPTTKOSAx7xegN1oGgifnLdmtDtsJU 356 | # OOhOtyf9Kp8e+EQQyPVrV/TNAgMBAAGjggHFMIIBwTAfBgNVHSMEGDAWgBRaxLl7 357 | # KgqjpepxA8Bg+S32ZXUOWDAdBgNVHQ4EFgQUTXqi+WoiTm5fYlDLqiDQ4I+uyckw 358 | # DgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHcGA1UdHwRwMG4w 359 | # NaAzoDGGL2h0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtY3Mt 360 | # ZzEuY3JsMDWgM6Axhi9odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1hc3N1 361 | # cmVkLWNzLWcxLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwDATAqMCgGCCsGAQUF 362 | # BwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEEATCBhAYI 363 | # KwYBBQUHAQEEeDB2MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j 364 | # b20wTgYIKwYBBQUHMAKGQmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp 365 | # Q2VydFNIQTJBc3N1cmVkSURDb2RlU2lnbmluZ0NBLmNydDAMBgNVHRMBAf8EAjAA 366 | # MA0GCSqGSIb3DQEBCwUAA4IBAQBT3M71SlOQ8vwM2txshp/XDvfoKBYHkpFCyanW 367 | # aFdsYQJQIKk4LOVgUJJ6LAf0xPSN7dZpjFaoilQy8Ajyd0U9UOnlEX4gk2J+z5i4 368 | # sFxK/W2KU1j6R9rY5LbScWtsV+X1BtHihpzPywGGE5eth5Q5TixMdI9CN3eWnKGF 369 | # kY13cI69zZyyTnkkb+HaFHZ8r6binvOyzMr69+oRf0Bv/uBgyBKjrmGEUxJZy+00 370 | # 7fbmYDEclgnWT1cRROarzbxmZ8R7Iyor0WU3nKRgkxan+8rzDhzpZdtgIFdYvjeO 371 | # c/IpPi2mI6NY4jqDXwkx1TEIbjUdrCmEfjhAfMTU094L7VSNMYIEXDCCBFgCAQEw 372 | # gYYwcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UE 373 | # CxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1 374 | # cmVkIElEIENvZGUgU2lnbmluZyBDQQIQBP3jqtvdtaueQfTZ1SF1TjAJBgUrDgMC 375 | # GgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYK 376 | # KwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG 377 | # 9w0BCQQxFgQU6U0f8MK2H45zwOn0W/EBcJ5pamswDQYJKoZIhvcNAQEBBQAEggEA 378 | # MnpRc6w2JuViHNoyRH1eHOjxZjhdj3+QoAQspNLOk3+26oAKWp8L1aIKOM21FD7o 379 | # mnjfRp15K5vdnV1Z6VQ5SFhq1q16nY+G5HJtO77M+/3S+/EkSsxOkZ0h8e5g7BIR 380 | # 4srsJoVZCe7cBOw2NmZO1L5jXUjroriwohSRIiugurQ0lLTFIC3vLCEwaKsWHyWO 381 | # QIQ+XM2MgcyrcX20U1lAuW7wniHjCRqbpu3MF989GcyHUE65el6awA2HcbWM0RqC 382 | # 9P7j4NJ7zDYdLtUIOCDeKChR9GD1jz7s74ox5NFV/fbjGA+Z8kRM0akiqunQM+9c 383 | # L5WeSM+XNaLUuaG51m9mhaGCAjAwggIsBgkqhkiG9w0BCQYxggIdMIICGQIBATCB 384 | # hjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL 385 | # ExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3Vy 386 | # ZWQgSUQgVGltZXN0YW1waW5nIENBAhANQkrgvjqI/2BAIc4UAPDdMA0GCWCGSAFl 387 | # AwQCAQUAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUx 388 | # DxcNMjIwNDAxMTYwNzEyWjAvBgkqhkiG9w0BCQQxIgQglhc8+5AyOUfWyUQgD5WA 389 | # 29G/dohtatDCrFdkBgDmwaYwDQYJKoZIhvcNAQEBBQAEggEAoVsNi+nttDmYBymi 390 | # H+dXkT6RaJ6yWu0S3oNATJjxu11axPOXwVDqHmKIuljOjB7ThyF7/HUvnzFE9axT 391 | # Aa8eUuHWzdkzWr0e0QEaP7YcdLjpvb9rFvxP6KFL1g+/4vOpax24iX4kUvGWpyW7 392 | # X07BIe8HOAp5PP3S0QSmRkAoB35EErLOhLl44Uu/wONVBy/W4+Vs8h+zTy1Zuf+J 393 | # uXl/yTY05HXkPwzS8vtkKyom2i7evPTpwxtbfDOhp/7dsUyPG4b+xpFlmeLNmGN9 394 | # q0QV3QN1+iYMxNSTp4XCebJNbimsMZMMXSa7+w3ywhdru1nbh7hWtWLq77Takv/u 395 | # 6tjW4g== 396 | # SIG # End signature block 397 | -------------------------------------------------------------------------------- /Pass json as parameters.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Read a JSON config file and call the specified script with parameters from it 4 | 5 | .PARAMETER jsonfile 6 | Path to the JSON file to read parameters from 7 | 8 | .PARAMETER script 9 | Name/path of the script to call with the parameters read from the JSON file and others specified by -otherParameters 10 | If not an absolute path, it will look in the current folder and the same folder as this script 11 | 12 | .PARAMETER otherParameters 13 | Other parameters to pass to the script. These will override the same parameter if found in the json file 14 | 15 | .PARAMETER saveTemplate 16 | Parse the script specified by -script and save its parameters to the file specified by -jsonfile as JSON 17 | 18 | .PARAMETER clobber 19 | If the file specified by -jsonfile exists when -savetemplate is specified, overwrite the file otherwise the script wil abort 20 | 21 | .EXAMPLE 22 | & '.\Pass json as parameters.ps1' -jsonfile C:\temp\parameters.json -script "New MCS catalog machines delivery group.ps1" -otherParameters @{ 'Verbose' = $true } 23 | 24 | Read parameters from the JSON file "C:\temp\parameters.json" and call the script "New MCS catalog machines delivery group.ps1" with these parameters and also -verbose 25 | 26 | .EXAMPLE 27 | & '.\Pass json as parameters.ps1' -jsonfile C:\temp\parameters.json -script "New MCS catalog machines delivery group.ps1" -savetemplate -clobber 28 | 29 | Parse the parameters in the script "New MCS catalog machines delivery group.ps1" and save these as JSON to file "C:\temp\parameters.json" 30 | 31 | .NOTES 32 | 33 | Modification History: 34 | 35 | 2021/12/09 @guyrleech Initial release (incorporating ideas from @KevinMarquette) 36 | 2021/12/09 @guyrleech Added -saveTemplate and -clobber options 37 | #> 38 | 39 | [CmdletBinding()] 40 | 41 | Param 42 | ( 43 | [Parameter(Mandatory=$true,HelpMessage='JSON file to use')] 44 | [string]$jsonfile , ## TODO read data from pipeline 45 | [Parameter(Mandatory=$true,HelpMessage='Script to use')] 46 | [string]$script , 47 | [hashtable]$otherParameters , 48 | [switch]$saveTemplate , 49 | [switch]$clobber 50 | ) 51 | 52 | [string]$scriptWithPath = $script 53 | 54 | if( -Not ( Test-Path -Path $scriptWithPath ) ) 55 | { 56 | if( -Not ( Test-Path -Path ($scriptWithPath = Join-Path -Path ((Get-Location -PSProvider FileSystem).Path) -ChildPath $script) ) ) 57 | { 58 | [string]$thisScriptPath = & { $MyInvocation.PSScriptRoot } 59 | 60 | if( -Not ( Test-Path -Path ($scriptWithPath = Join-Path -Path $thisScriptPath -ChildPath $script) ) ) 61 | { 62 | Throw "Unable to find script `"$script`"" 63 | } 64 | } 65 | } 66 | 67 | $scriptParameters = Get-Command -Name $scriptWithPath | Select-Object -ExpandProperty Parameters 68 | 69 | if( $null -eq $scriptParameters -or $scriptParameters.Count -eq 0 ) 70 | { 71 | Throw "Script `"$scriptParameters`" does not take any parameters" 72 | } 73 | 74 | Write-Verbose -Message "Script `"$scriptWithPath`" takes $($scriptParameters.Count) parameters" 75 | 76 | if( $saveTemplate ) 77 | { 78 | if( ( Test-Path -Path $jsonfile ) -and -Not $clobber ) 79 | { 80 | Throw "File `"$jsonfile`" exists - use -clobber to overwrite" 81 | } 82 | 83 | $jsonobject = New-Object -TypeName psobject 84 | 85 | ## parse the file as Get-Command doesn't tell us if there are default values which we need to put in the json 86 | if( -Not ( $ast = [System.Management.Automation.Language.Parser]::ParseFile( $scriptwithpath , [ref]$null , [ref]$null ) ) ) 87 | { 88 | Throw "Failed to parse script `"$scriptWithPath`"" 89 | } 90 | 91 | ForEach( $parameter in $ast.paramblock.Parameters ) 92 | { 93 | ## if pscredential, add place holders for username and securestring 94 | $value = $null 95 | 96 | if( $parameter.StaticType.Name -eq 'PSCredential' ) 97 | { 98 | $value = @{ 99 | 'Password' = 'SecureString text' 100 | 'UserName' = 'domain\user' 101 | } 102 | } 103 | else 104 | { 105 | if( $parameter.StaticType.BaseType -match '\bArray\b' ) 106 | { 107 | if( $parameter.defaultvalue ) 108 | { 109 | $value = Invoke-Expression -Command $parameter.defaultvalue.extent.text 110 | } 111 | else 112 | { 113 | ## add dummy entry so that we get a place holder in the json 114 | $value = @( ) 115 | } 116 | } 117 | else 118 | { 119 | Set-Variable -Name 'value' -Value (( $parameter.DefaultValue | Select-Object -ExpandProperty Value -ErrorAction SilentlyContinue ) -as ($parameter.StaticType -as [type])) 120 | } 121 | } 122 | Add-Member -InputObject $jsonobject -MemberType NoteProperty -Name ($parameter.Name -replace '^\$') -Value $value 123 | } 124 | 125 | $jsonobject | ConvertTo-Json | Out-File -FilePath $jsonfile 126 | } 127 | else ## not saving to a template 128 | { 129 | [hashtable]$parametersFromJson = @{} 130 | 131 | if( -Not [string]::IsNullOrEmpty( $jsonfile ) ) 132 | { 133 | if( -Not ( Test-Path -Path $jsonfile -ErrorAction SilentlyContinue ) ) 134 | { 135 | Throw "JSON file `"$jsonfile`" not found" 136 | } 137 | if( $jsonobject = ( Get-Content -Path $jsonfile | ConvertFrom-Json ) ) 138 | { 139 | ForEach( $property in $jsonobject.PSObject.Properties ) 140 | { 141 | if( $otherParameters.Contains( $property.Name )) 142 | { 143 | Write-Warning -Message "-$($property.Name) present in other parameters passed so ignoring json value `"$($property.Value)`"" 144 | } 145 | else 146 | { 147 | $value = $property.value 148 | 149 | ## need to deal with [switch] types as will be custom objects 150 | if( $property.Value.GetType().Name -eq 'PSCustomObject' ) 151 | { 152 | if( $property.Value.PSObject.Properties[ 'IsPresent' ] -and $property.Value.IsPresent -is [bool] ) 153 | { 154 | $value = $property.Value.IsPresent 155 | } 156 | elseif( $property.Name -match 'credentials$' -and $property.Value.PSobject.Properties[ 'username' ] -and $property.Value.PSobject.Properties[ 'password' ] ) 157 | { 158 | $value = $null 159 | $value = New-Object System.Management.Automation.PSCredential( $property.Value.username , ( ConvertTo-SecureString -String $property.Value.password )) 160 | if( -not $value ) 161 | { 162 | Throw "Failed to make PScredential for $($property.Value.username)" 163 | } 164 | } 165 | else 166 | { 167 | Throw "Don't know how to process json file item `"$($property.Name)`"" 168 | } 169 | } 170 | 171 | try 172 | { 173 | Write-Verbose -Message "Adding `"$($property.Name)`" = `"$Value`"" 174 | $parametersFromJson.Add( $property.Name , $value ) 175 | } 176 | catch 177 | { 178 | if( ( $existing = $parametersFromJson[ $property.Name ] ) -ne $value ) 179 | { 180 | Throw "Repetition of `"$($property.Name)`" with different values `"$value`" and `"$existing`"" 181 | } 182 | } 183 | } 184 | } 185 | } 186 | if( -Not $jsonobject ) 187 | { 188 | Throw "Failed to parse json from `"$jsonfile`"" 189 | } 190 | } 191 | 192 | Write-Verbose -Message "Got $($parametersFromJson.Count) parameters from json to pass" 193 | 194 | $otherParameters += $parametersFromJson 195 | 196 | Write-Verbose -Message "Calling script `"$scriptWithPath`" with $($otherParameters.Count) parameters" 197 | 198 | & $scriptWithPath @otherParameters 199 | } 200 | 201 | # SIG # Begin signature block 202 | # MIIZsAYJKoZIhvcNAQcCoIIZoTCCGZ0CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB 203 | # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR 204 | # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU97kjt8l1PsQQRzB64YjwCHYV 205 | # b/ugghS+MIIE/jCCA+agAwIBAgIQDUJK4L46iP9gQCHOFADw3TANBgkqhkiG9w0B 206 | # AQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD 207 | # VQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFz 208 | # c3VyZWQgSUQgVGltZXN0YW1waW5nIENBMB4XDTIxMDEwMTAwMDAwMFoXDTMxMDEw 209 | # NjAwMDAwMFowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu 210 | # MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMTCCASIwDQYJKoZIhvcN 211 | # AQEBBQADggEPADCCAQoCggEBAMLmYYRnxYr1DQikRcpja1HXOhFCvQp1dU2UtAxQ 212 | # tSYQ/h3Ib5FrDJbnGlxI70Tlv5thzRWRYlq4/2cLnGP9NmqB+in43Stwhd4CGPN4 213 | # bbx9+cdtCT2+anaH6Yq9+IRdHnbJ5MZ2djpT0dHTWjaPxqPhLxs6t2HWc+xObTOK 214 | # fF1FLUuxUOZBOjdWhtyTI433UCXoZObd048vV7WHIOsOjizVI9r0TXhG4wODMSlK 215 | # XAwxikqMiMX3MFr5FK8VX2xDSQn9JiNT9o1j6BqrW7EdMMKbaYK02/xWVLwfoYer 216 | # vnpbCiAvSwnJlaeNsvrWY4tOpXIc7p96AXP4Gdb+DUmEvQECAwEAAaOCAbgwggG0 217 | # MA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsG 218 | # AQUFBwMIMEEGA1UdIAQ6MDgwNgYJYIZIAYb9bAcBMCkwJwYIKwYBBQUHAgEWG2h0 219 | # dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAfBgNVHSMEGDAWgBT0tuEgHf4prtLk 220 | # YaWyoiWyyBc1bjAdBgNVHQ4EFgQUNkSGjqS6sGa+vCgtHUQ23eNqerwwcQYDVR0f 221 | # BGowaDAyoDCgLoYsaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJl 222 | # ZC10cy5jcmwwMqAwoC6GLGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFz 223 | # c3VyZWQtdHMuY3JsMIGFBggrBgEFBQcBAQR5MHcwJAYIKwYBBQUHMAGGGGh0dHA6 224 | # Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBPBggrBgEFBQcwAoZDaHR0cDovL2NhY2VydHMu 225 | # ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkFzc3VyZWRJRFRpbWVzdGFtcGluZ0NB 226 | # LmNydDANBgkqhkiG9w0BAQsFAAOCAQEASBzctemaI7znGucgDo5nRv1CclF0CiNH 227 | # o6uS0iXEcFm+FKDlJ4GlTRQVGQd58NEEw4bZO73+RAJmTe1ppA/2uHDPYuj1UUp4 228 | # eTZ6J7fz51Kfk6ftQ55757TdQSKJ+4eiRgNO/PT+t2R3Y18jUmmDgvoaU+2QzI2h 229 | # F3MN9PNlOXBL85zWenvaDLw9MtAby/Vh/HUIAHa8gQ74wOFcz8QRcucbZEnYIpp1 230 | # FUL1LTI4gdr0YKK6tFL7XOBhJCVPst/JKahzQ1HavWPWH1ub9y4bTxMd90oNcX6X 231 | # t/Q/hOvB46NJofrOp79Wz7pZdmGJX36ntI5nePk2mOHLKNpbh6aKLzCCBTAwggQY 232 | # oAMCAQICEAQJGBtf1btmdVNDtW+VUAgwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UE 233 | # BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj 234 | # ZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4X 235 | # DTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcjELMAkGA1UEBhMCVVMxFTAT 236 | # BgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEx 237 | # MC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2lnbmluZyBD 238 | # QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPjTsxx/DhGvZ3cH0wsx 239 | # SRnP0PtFmbE620T1f+Wondsy13Hqdp0FLreP+pJDwKX5idQ3Gde2qvCchqXYJawO 240 | # eSg6funRZ9PG+yknx9N7I5TkkSOWkHeC+aGEI2YSVDNQdLEoJrskacLCUvIUZ4qJ 241 | # RdQtoaPpiCwgla4cSocI3wz14k1gGL6qxLKucDFmM3E+rHCiq85/6XzLkqHlOzEc 242 | # z+ryCuRXu0q16XTmK/5sy350OTYNkO/ktU6kqepqCquE86xnTrXE94zRICUj6whk 243 | # PlKWwfIPEvTFjg/BougsUfdzvL2FsWKDc0GCB+Q4i2pzINAPZHM8np+mM6n9Gd8l 244 | # k9ECAwEAAaOCAc0wggHJMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQD 245 | # AgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHkGCCsGAQUFBwEBBG0wazAkBggrBgEF 246 | # BQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAChjdodHRw 247 | # Oi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0Eu 248 | # Y3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20v 249 | # RGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRwOi8vY3JsMy5k 250 | # aWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsME8GA1UdIARI 251 | # MEYwOAYKYIZIAYb9bAACBDAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdp 252 | # Y2VydC5jb20vQ1BTMAoGCGCGSAGG/WwDMB0GA1UdDgQWBBRaxLl7KgqjpepxA8Bg 253 | # +S32ZXUOWDAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkqhkiG 254 | # 9w0BAQsFAAOCAQEAPuwNWiSz8yLRFcgsfCUpdqgdXRwtOhrE7zBh134LYP3DPQ/E 255 | # r4v97yrfIFU3sOH20ZJ1D1G0bqWOWuJeJIFOEKTuP3GOYw4TS63XX0R58zYUBor3 256 | # nEZOXP+QsRsHDpEV+7qvtVHCjSSuJMbHJyqhKSgaOnEoAjwukaPAJRHinBRHoXpo 257 | # aK+bp1wgXNlxsQyPu6j4xRJon89Ay0BEpRPw5mQMJQhCMrI2iiQC/i9yfhzXSUWW 258 | # 6Fkd6fp0ZGuy62ZD2rOwjNXpDd32ASDOmTFjPQgaGLOBm0/GkxAG/AeB+ova+YJJ 259 | # 92JuoVP6EpQYhS6SkepobEQysmah5xikmmRR7zCCBTEwggQZoAMCAQICEAqhJdbW 260 | # Mht+QeQF2jaXwhUwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCVVMxFTATBgNV 261 | # BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIG 262 | # A1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTE2MDEwNzEyMDAw 263 | # MFoXDTMxMDEwNzEyMDAwMFowcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lD 264 | # ZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGln 265 | # aUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQTCCASIwDQYJKoZI 266 | # hvcNAQEBBQADggEPADCCAQoCggEBAL3QMu5LzY9/3am6gpnFOVQoV7YjSsQOB0Uz 267 | # URB90Pl9TWh+57ag9I2ziOSXv2MhkJi/E7xX08PhfgjWahQAOPcuHjvuzKb2Mln+ 268 | # X2U/4Jvr40ZHBhpVfgsnfsCi9aDg3iI/Dv9+lfvzo7oiPhisEeTwmQNtO4V8CdPu 269 | # XciaC1TjqAlxa+DPIhAPdc9xck4Krd9AOly3UeGheRTGTSQjMF287DxgaqwvB8z9 270 | # 8OpH2YhQXv1mblZhJymJhFHmgudGUP2UKiyn5HU+upgPhH+fMRTWrdXyZMt7HgXQ 271 | # hBlyF/EXBu89zdZN7wZC/aJTKk+FHcQdPK/P2qwQ9d2srOlW/5MCAwEAAaOCAc4w 272 | # ggHKMB0GA1UdDgQWBBT0tuEgHf4prtLkYaWyoiWyyBc1bjAfBgNVHSMEGDAWgBRF 273 | # 66Kv9JLLgjEtUYunpyGd823IDzASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB 274 | # /wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDCDB5BggrBgEFBQcBAQRtMGswJAYI 275 | # KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3 276 | # aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9v 277 | # dENBLmNydDCBgQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQu 278 | # Y29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2Ny 279 | # bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBQBgNV 280 | # HSAESTBHMDgGCmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cu 281 | # ZGlnaWNlcnQuY29tL0NQUzALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggEB 282 | # AHGVEulRh1Zpze/d2nyqY3qzeM8GN0CE70uEv8rPAwL9xafDDiBCLK938ysfDCFa 283 | # KrcFNB1qrpn4J6JmvwmqYN92pDqTD/iy0dh8GWLoXoIlHsS6HHssIeLWWywUNUME 284 | # aLLbdQLgcseY1jxk5R9IEBhfiThhTWJGJIdjjJFSLK8pieV4H9YLFKWA1xJHcLN1 285 | # 1ZOFk362kmf7U2GJqPVrlsD0WGkNfMgBsbkodbeZY4UijGHKeZR+WfyMD+NvtQEm 286 | # tmyl7odRIeRYYJu6DC0rbaLEfrvEJStHAgh8Sa4TtuF8QkIoxhhWz0E0tmZdtnR7 287 | # 9VYzIi8iNrJLokqV2PWmjlIwggVPMIIEN6ADAgECAhAE/eOq2921q55B9NnVIXVO 288 | # MA0GCSqGSIb3DQEBCwUAMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy 289 | # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lD 290 | # ZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwHhcNMjAwNzIwMDAw 291 | # MDAwWhcNMjMwNzI1MTIwMDAwWjCBizELMAkGA1UEBhMCR0IxEjAQBgNVBAcTCVdh 292 | # a2VmaWVsZDEmMCQGA1UEChMdU2VjdXJlIFBsYXRmb3JtIFNvbHV0aW9ucyBMdGQx 293 | # GDAWBgNVBAsTD1NjcmlwdGluZ0hlYXZlbjEmMCQGA1UEAxMdU2VjdXJlIFBsYXRm 294 | # b3JtIFNvbHV0aW9ucyBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB 295 | # AQCvbSdd1oAAu9rTtdnKSlGWKPF8g+RNRAUDFCBdNbYbklzVhB8hiMh48LqhoP7d 296 | # lzZY3YmuxztuPlB7k2PhAccd/eOikvKDyNeXsSa3WaXLNSu3KChDVekEFee/vR29 297 | # mJuujp1eYrz8zfvDmkQCP/r34Bgzsg4XPYKtMitCO/CMQtI6Rnaj7P6Kp9rH1nVO 298 | # /zb7KD2IMedTFlaFqIReT0EVG/1ZizOpNdBMSG/x+ZQjZplfjyyjiYmE0a7tWnVM 299 | # Z4KKTUb3n1CTuwWHfK9G6CNjQghcFe4D4tFPTTKOSAx7xegN1oGgifnLdmtDtsJU 300 | # OOhOtyf9Kp8e+EQQyPVrV/TNAgMBAAGjggHFMIIBwTAfBgNVHSMEGDAWgBRaxLl7 301 | # KgqjpepxA8Bg+S32ZXUOWDAdBgNVHQ4EFgQUTXqi+WoiTm5fYlDLqiDQ4I+uyckw 302 | # DgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHcGA1UdHwRwMG4w 303 | # NaAzoDGGL2h0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtY3Mt 304 | # ZzEuY3JsMDWgM6Axhi9odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1hc3N1 305 | # cmVkLWNzLWcxLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwDATAqMCgGCCsGAQUF 306 | # BwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEEATCBhAYI 307 | # KwYBBQUHAQEEeDB2MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j 308 | # b20wTgYIKwYBBQUHMAKGQmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp 309 | # Q2VydFNIQTJBc3N1cmVkSURDb2RlU2lnbmluZ0NBLmNydDAMBgNVHRMBAf8EAjAA 310 | # MA0GCSqGSIb3DQEBCwUAA4IBAQBT3M71SlOQ8vwM2txshp/XDvfoKBYHkpFCyanW 311 | # aFdsYQJQIKk4LOVgUJJ6LAf0xPSN7dZpjFaoilQy8Ajyd0U9UOnlEX4gk2J+z5i4 312 | # sFxK/W2KU1j6R9rY5LbScWtsV+X1BtHihpzPywGGE5eth5Q5TixMdI9CN3eWnKGF 313 | # kY13cI69zZyyTnkkb+HaFHZ8r6binvOyzMr69+oRf0Bv/uBgyBKjrmGEUxJZy+00 314 | # 7fbmYDEclgnWT1cRROarzbxmZ8R7Iyor0WU3nKRgkxan+8rzDhzpZdtgIFdYvjeO 315 | # c/IpPi2mI6NY4jqDXwkx1TEIbjUdrCmEfjhAfMTU094L7VSNMYIEXDCCBFgCAQEw 316 | # gYYwcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UE 317 | # CxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1 318 | # cmVkIElEIENvZGUgU2lnbmluZyBDQQIQBP3jqtvdtaueQfTZ1SF1TjAJBgUrDgMC 319 | # GgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYK 320 | # KwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG 321 | # 9w0BCQQxFgQUPZn4J17fvQIN4CXRvR2U+yjbSWUwDQYJKoZIhvcNAQEBBQAEggEA 322 | # Q5Qp0pRB2tGPfCPfOED8ZaivxgYV04VvnRCn076Ocb5005CTQG3VPRLV99sevn6G 323 | # fb95I3m8oxi1HSdZlD8XrAEoyQ2NxzyQKW+5BguNxdcQh2avdI5EQl0rncYmC1J7 324 | # jpjtVIA9fG/D/cmH3dIf27w7Bc66JGKJcIRyRpr3Lq5P3xz5qHgTKTB1+vQM/ERT 325 | # LCLG8WxxKjxKeF5tG3eoAkcqrOiKhgPVpmgdK+2uufKREL3lE+vshpeNCIZ2Gi3U 326 | # KeZjB4B64193a3EB35IKrGJmO6FrrvWcmPjnhnfxOOgbbaoJmbBIqp6SG/UDq6Hi 327 | # 5kQOeON7KdBEb+kYzbEbt6GCAjAwggIsBgkqhkiG9w0BCQYxggIdMIICGQIBATCB 328 | # hjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL 329 | # ExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3Vy 330 | # ZWQgSUQgVGltZXN0YW1waW5nIENBAhANQkrgvjqI/2BAIc4UAPDdMA0GCWCGSAFl 331 | # AwQCAQUAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUx 332 | # DxcNMjExMjA5MjMxNjUxWjAvBgkqhkiG9w0BCQQxIgQgWmX3Jb292nYPVJl+vi+u 333 | # grtFXKbxPsZ4s1Q1HBlxIaUwDQYJKoZIhvcNAQEBBQAEggEAKi8XUh2LVX8KITKt 334 | # DnMaTW45TMKo19WMqiHVzSj2RMz3TFMdLPamqhRd5IpljMNh6RwG0Vu7KOiDnP4k 335 | # 41oFNDLfsGmIHkuvyYgY92C3zT/Md+/U+AektWBM0rsQ3udt4WbiNkRKnKilknNP 336 | # +BE65hL2GjldRr0s8pzZUCRA+AbKWsExyNHvOf2nMbw5vrf0CcdwzazWtxJuUHX4 337 | # iZNPE1JhJNp5dD8MKfnahek2bndLtcK/0PAyNqHqYVni6OB6EYI8bR7GaluwpUSo 338 | # YNZ97F6BhwV36q+ekTXgMNWuDcAU5C9XsPGuZglqz07Rp5Xqbj0Eh0AODaa0MA82 339 | # dekIgQ== 340 | # SIG # End signature block 341 | -------------------------------------------------------------------------------- /Query SQLite database.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Perform a query on a SQLite database 3 | 4 | Requires SQLite libraries - https://sqlitebrowser.org/dl/ 5 | 6 | @guyrleech 2019 7 | #> 8 | 9 | <# 10 | .SYNOPSIS 11 | 12 | Perform a select query on a specified SQLite database contained within a file or show the tables within the database. 13 | 14 | .DESCRIPTION 15 | 16 | Requires SQLite libraries - https://sqlitebrowser.org/dl/ 17 | 18 | .PARAMETER database 19 | 20 | The path to an existing SQLite database file 21 | 22 | .PARAMETER table 23 | 24 | The name of the table to query. If not specified then all table names will be displayed. 25 | 26 | .PARAMETER select 27 | 28 | The comma separated list of columns to retrieve. Default is all. 29 | 30 | .PARAMETER where 31 | 32 | An optional where clause to limite the result set 33 | 34 | .PARAMETER outputFile 35 | 36 | The path to a non-existent csv file to retrieve the results. Will write to an on-screen sortable/filterable grid view if not specified. 37 | 38 | .PARAMETER sqlite 39 | 40 | The path to a 'System.Data.SQLite.dll' file in order to retrieve the required cmdlets 41 | 42 | .EXAMPLE 43 | 44 | & '.\Query SQLite database.ps1' -database "c:\temp\AnalysisDatabase.sqlite" -table PDM_Application -where "Computer like 'Laptop10.%'" -verbose 45 | 46 | Retrieve all rows from the table 'PDM_Application' in the specified database file where the 'Computer' field is like 'Laptop10.' and display in a grid view 47 | 48 | & '.\Query SQLite database.ps1' -database "c:\temp\AnalysisDatabase.sqlite" -table PDM_Application -outputFile apps.csv 49 | 50 | Retrieve all rows from the table 'PDM_Application' in the specified database file and write to the file 'apps.csv' 51 | 52 | #> 53 | 54 | [CmdletBinding()] 55 | 56 | Param 57 | ( 58 | [Parameter(Mandatory=$true)] 59 | [ValidateScript({Test-Path -Path $_ -PathType 'Leaf' })] 60 | [string]$database , 61 | [string]$table , 62 | [string]$select = '*' , 63 | [string]$where , 64 | [string]$outputFile , 65 | [string]$sqlite = "$env:ProgramFiles\System.Data.SQLite\2010\bin\System.Data.SQLite.dll" 66 | ) 67 | 68 | if( ! ( Get-ItemProperty -Path $database -ErrorAction Stop | Select -ExpandProperty Length ) ) 69 | { 70 | Throw "Database file `"$database`" is zero length" 71 | } 72 | 73 | Add-Type -Path $sqlite -ErrorAction Stop 74 | 75 | $dbConnection = New-Object -TypeName System.Data.SQLite.SQLiteConnection 76 | $dbConnection.ConnectionString = "Data Source=$database" 77 | $dbConnection.Open() 78 | 79 | ## Get table names 80 | $sql = $dbConnection.CreateCommand() 81 | $adapter = New-Object -TypeName System.Data.SQLite.SQLiteDataAdapter $sql 82 | 83 | if( ! $PSBoundParameters[ 'table' ] ) 84 | { 85 | $sql.CommandText = 'SELECT * FROM sqlite_master WHERE type=''table''' 86 | $data = New-Object System.Data.DataSet 87 | [void]$adapter.Fill($data) 88 | 89 | if( $data ) 90 | { 91 | if( $data.Tables -and $data.Tables.Count ) 92 | { 93 | $data.Tables | Format-Table -AutoSize 94 | } 95 | else 96 | { 97 | Write-Warning "No tables found in database" 98 | } 99 | } 100 | else 101 | { 102 | Write-Error 'Failed to retrieve table list' 103 | } 104 | } 105 | else 106 | { 107 | $sql.CommandText = "SELECT $select FROM $table" 108 | 109 | if( $PSBoundParameters[ 'where' ] ) 110 | { 111 | $sql.CommandText += " where $where" 112 | } 113 | $data = New-Object System.Data.DataSet 114 | [void]$adapter.Fill($data) 115 | try 116 | { 117 | if( $data.Tables -and $data.Tables.Rows -and $data.Tables.Rows.Count ) 118 | { 119 | Write-Verbose ([string]$title = "Got $($data.Tables.Rows.Count) rows from query $($sql.CommandText)" ) 120 | if( $PSBoundParameters[ 'outputFile' ] ) 121 | { 122 | $data.Tables.Rows | Export-Csv -Path $outputFile -NoClobber -NoTypeInformation 123 | } 124 | else 125 | { 126 | $selected = $data.Tables.Rows | Out-GridView -Title $title -PassThru 127 | if( $selected -and $selected.Count ) 128 | { 129 | $selected | clip.exe 130 | } 131 | } 132 | } 133 | else 134 | { 135 | Write-Warning "No rows returned from query $($sql.CommandText)" 136 | } 137 | } 138 | catch 139 | { 140 | Write-Warning "Failed to get any results from query $($sql.CommandText)" 141 | } 142 | } 143 | 144 | if( $dbConnection ) 145 | { 146 | $dbConnection.Close() 147 | $dbConnection.Dispose() 148 | } 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # General-Scripts 2 | Scripts for diagnosis, troubleshooting, automation, etc. 3 | -------------------------------------------------------------------------------- /Regrecent.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 3 2 | 3 | <# 4 | Regrecent in PowerShell - find registry keys modified in a given time window 5 | 6 | Guy Leech, 2018 7 | 8 | Modification History 9 | 10 | 20/12/2017 GRL Added -sinceBoot parameter 11 | 12 | 12/02/2018 GRL Added grid view output and date format options. Convert non-PowerShell registry provider paths. Added support for HKEY_CLASSES_ROOT 13 | #> 14 | 15 | <# 16 | .SYNOPSIS 17 | 18 | Show registry keys last modified within a given time/date range 19 | 20 | .DESCRIPTION 21 | 22 | Registry keys, but not values, contain timestamps recording when they were last modified, e.g. when a registry value in that key was changed. 23 | This script will find keys changed in a given time range to aid troubleshooting of problems which may be related to changed registry settings, e.g. my machine started crashing yesterday so let 24 | us see what registry keys have been changed since yesterday. 25 | 26 | .PARAMETER key 27 | 28 | The starting key/path to use. 29 | 30 | .PARAMETER last 31 | 32 | Show changed keys in the preceding period where 's' is seconds, 'm' is minutes, 'h' is hours, 'd' is days, 'w' is weeks and 'y' is years so 12h will show all keys changed in the last 12 hours. 33 | 34 | .PARAMETER start 35 | 36 | The start date/time in which to look for changes. If no date is given then the current day is used and if no time is given then midnight on that day is used. E.g. "26/01/17 07:00:00" 37 | 38 | .PARAMETER end 39 | 40 | The end date/time in which to look for changes. If no date is given then the current day is used and if no time is given then midnight on that day is used. E.g. "26/01/17 23:00:00" 41 | 42 | .PARAMETER notin 43 | 44 | Registry keys not changed in the specified time window are reported 45 | 46 | .PARAMETER includes 47 | 48 | A comma separated list of regex patterns to only show registry changed keys that match that pattern. E.g. "\\Sheet" will only show keys which start with "\Sheet" 49 | 50 | .PARAMETER excludes 51 | 52 | A comma separated list of regex patterns to not show registry changed keys that match that pattern. E.g. "\\Enum" will not show any keys which start with "\Enum" 53 | 54 | .PARAMETER delimiter 55 | 56 | The delimiter to use between the key name and datestamp in the output. The default is a colon. 57 | 58 | .PARAMETER dateFormat 59 | 60 | The date format to use in the output. See https://blogs.technet.microsoft.com/heyscriptingguy/2015/01/22/formatting-date-strings-with-powershell/ 61 | 62 | .PARAMETER outputFile 63 | 64 | Will write results to the specified csv file 65 | 66 | .PARAMETER gridView 67 | 68 | Show results in a gridview 69 | 70 | .EXAMPLE 71 | 72 | .\regrecent.ps1 -key HKCU:\Software -last 1d 73 | 74 | Show all registry keys in HKCU\Software changed in the last 1 day 75 | 76 | .EXAMPLE 77 | 78 | .\regrecent.ps1 -key HKLM:\Software -start "01/01/17" -end "31/01/17" -gridview 79 | 80 | Show all registry keys in HKLM\Software changed in January 2017 in a grid view 81 | 82 | .EXAMPLE 83 | 84 | .\regrecent.ps1 -key HKEY_LOCAL_MACHINE\Software\Microsoft -start "01/01/17" -end "31/01/17" -notin 85 | 86 | Show all registry keys in HKLM\Software\Microsoft not changed in January 2017 87 | 88 | .EXAMPLE 89 | 90 | .\regrecent.ps1 -key HKLM\System\CurrentControlSet -last 36h -exclude '\\Enum','\\Linkage' -outputFile registry.changes.csv 91 | 92 | Show all registry keys in HKLM\System\CurrentControlSet, except for keys beginning \Enum and \Linkage, which have changed in the last 36 hours and output to a csv file. 93 | Note the escaping of the backslash characters in the -exclude arguments since they are regualr expressions 94 | 95 | #> 96 | 97 | [CmdletBinding()] 98 | 99 | Param 100 | ( 101 | [Parameter(Mandatory=$true)] 102 | [string]$key , 103 | [Parameter(ParameterSetName='Last')] 104 | [string]$last , 105 | [Parameter(ParameterSetName='SinceBoot')] 106 | [switch]$sinceBoot , 107 | [Parameter(ParameterSetName='StartTime')] 108 | [string]$start , 109 | [string]$end , 110 | [switch]$notin , 111 | [string[]]$excludes , 112 | [string[]]$includes , 113 | [string]$delimiter = ':' , 114 | [string]$outputFile , 115 | [string]$dateFormat = 'G' , 116 | [switch]$gridView 117 | ) 118 | 119 | [hashtable]$regKeyConversions = ` 120 | @{ 121 | 'HKEY_CLASSES_ROOT' = 'HKCR' 122 | 'HKEY_CURRENT_USER' = 'HKCU' 123 | 'HKEY_LOCAL_MACHINE' = 'HKLM' 124 | 'HKEY_USERS' = 'HKU' 125 | } 126 | 127 | Add-Type @" 128 | using System; 129 | using System.Text; 130 | using System.Runtime.InteropServices; 131 | 132 | namespace PInvoke.Win32 { 133 | 134 | public class advapi32 { 135 | [DllImport("advapi32.dll", CharSet = CharSet.Auto)] 136 | public static extern Int32 RegQueryInfoKey( 137 | Microsoft.Win32.SafeHandles.SafeRegistryHandle hKey, 138 | out UInt32 lpClass, 139 | [In, Out] ref UInt32 lpcbClass, 140 | UInt32 lpReserved, 141 | out UInt32 lpcSubKeys, 142 | out UInt32 lpcbMaxSubKeyLen, 143 | out UInt32 lpcbMaxClassLen, 144 | out UInt32 lpcValues, 145 | out UInt32 lpcbMaxValueNameLen, 146 | out UInt32 lpcbMaxValueLen, 147 | out UInt32 lpcbSecurityDescriptor, 148 | out Int64 lpftLastWriteTime 149 | ); 150 | } 151 | } 152 | "@ 153 | 154 | Function Get-RegTimeStamp 155 | { 156 | [OutputType([datetime])] 157 | Param 158 | ( 159 | $regkey 160 | ) 161 | 162 | $LastWriteTime = $null 163 | 164 | if( $regKey ) 165 | { 166 | $result = [PInvoke.win32.advapi32]::RegQueryInfoKey( $regKey.Handle , 167 | [ref] $null, # ClassName, 168 | [ref] $null, # ClassLength, 169 | $null, # Reserved 170 | [ref] $null, # SubKeyCount 171 | [ref] $null, # MaxSubKeyNameLength 172 | [ref] $null, # MaxClassLength 173 | [ref] $null, # ValueCount 174 | [ref] $null, # MaxValueNameLength 175 | [ref] $null, # MaxValueValueLength 176 | [ref] $null, # SecurityDescriptorSize 177 | [ref] $LastWriteTime ) 178 | 179 | if( $result -eq 0 -and $LastWriteTime ) 180 | { 181 | $LastWriteTime = [datetime]::FromFileTime( $LastWriteTime ) 182 | } 183 | } 184 | $LastWriteTime 185 | } 186 | 187 | if( ! [string]::IsNullOrEmpty( $last ) ) 188 | { 189 | ## see what last character is as will tell us what units to work with 190 | [int]$multiplier = 0 191 | switch( $last[-1] ) 192 | { 193 | "s" { $multiplier = 1 } 194 | "m" { $multiplier = 60 } 195 | "h" { $multiplier = 3600 } 196 | "d" { $multiplier = 86400 } 197 | "w" { $multiplier = 86400 * 7 } 198 | "y" { $multiplier = 86400 * 365 } 199 | default { Write-Error "Unknown multiplier `"$($last[-1])`"" ; return } 200 | } 201 | $endDate = Get-Date 202 | if( $last.Length -le 1 ) 203 | { 204 | $startDate = $endDate.AddSeconds( -$multiplier ) 205 | } 206 | else 207 | { 208 | $startDate = $endDate.AddSeconds( - ( ( $last.Substring( 0 ,$last.Length - 1 ) -as [int] ) * $multiplier ) ) 209 | } 210 | } 211 | else 212 | { 213 | if( $sinceBoot ) 214 | { 215 | if( Get-Command Get-CimInstance -ErrorAction SilentlyContinue ) 216 | { 217 | $startDate = Get-CimInstance -ClassName Win32_OperatingSystem | Select -ExpandProperty lastbootuptime 218 | } 219 | else 220 | { 221 | Write-Warning ( "Cannot use -sinceBoot with PowerShell version {0}, requires minimum 3.0" -f $PSVersionTable.PSVersion.Major ) 222 | return 223 | } 224 | } 225 | elseif( ! $start ) 226 | { 227 | Write-Error "Must specify at least a start date/time with -start or -last" 228 | return 229 | } 230 | else 231 | { 232 | $startDate = [datetime]::Parse( $start ) 233 | } 234 | if( ! $end ) 235 | { 236 | $endDate = Get-Date 237 | } 238 | else 239 | { 240 | $endDate = [datetime]::Parse( $end ) 241 | } 242 | } 243 | 244 | ## If key name is long format, like would get when copied from regedit, convert to PoSH reg provider path 245 | [string]$changedKey = $regKeyConversions[ ( $key -split '\\' )[0] ] 246 | 247 | if( ! [string]::IsNullOrEmpty( $changedKey ) ) 248 | { 249 | $key = $key -replace '^HK[A-Z_]+' , $changedKey 250 | } 251 | 252 | ## If PoSH reg provider style path not given, as in missing :, then change it 253 | if( $key -match '^(HK[A-Z]{2})\\[^:]' ) 254 | { 255 | $key = $key -replace "^$($Matches[1])" ,( $Matches[1] + ':' ) 256 | } 257 | 258 | ## Don't have out of the box paths for HKCR and HKU 259 | if( $key -match '^(HKU|HKCR)' ) 260 | { 261 | try 262 | { 263 | $provider = Get-PSDrive -Name $matches[1] -ErrorAction SilentlyContinue 264 | } 265 | catch 266 | { 267 | $provider = $null 268 | } 269 | 270 | if( ! $provider ) 271 | { 272 | [string]$rootKey = $null 273 | 274 | $regKeyConversions.GetEnumerator() | ForEach-Object ` 275 | { 276 | if( $_.Value -eq $Matches[1] -and [string]::IsNullOrEmpty( $rootKey ) ) 277 | { 278 | $rootKey = $_.Key 279 | } 280 | } 281 | 282 | $null = New-PSDrive -Name $Matches[1] -PSProvider Registry -Root "Registry::$rootKey" 283 | } 284 | } 285 | 286 | ## Check top key as we'll be silent for other failures 287 | if( ! ( Get-Item $key -EA Stop ) ) 288 | { 289 | return 290 | } 291 | 292 | $results = @( Get-ChildItem -Path $key -Recurse -EA SilentlyContinue | ForEach-Object ` 293 | { 294 | $thisKey = $_ 295 | [bool]$excluded = $false 296 | if( $excludes -and $excludes.Count -gt 0 ) 297 | { 298 | ForEach( $exclude in $excludes ) 299 | { 300 | if( $thisKey -match $exclude ) 301 | { 302 | $excluded = $true 303 | break 304 | } 305 | } 306 | } 307 | if( ! $excluded ) 308 | { 309 | if( $includes -and $includes.Count -gt 0 ) 310 | { 311 | $excluded = $true 312 | ForEach( $include in $includes ) 313 | { 314 | if( $thisKey -match $include ) 315 | { 316 | $excluded = $false 317 | break 318 | } 319 | } 320 | } 321 | else 322 | { 323 | $excluded = $false 324 | } 325 | } 326 | if( ! $excluded ) 327 | { 328 | $lastwritten = Get-RegTimeStamp $thisKey 329 | if( $lastWritten ) 330 | { 331 | if( ( ! $notin -and $lastWritten -ge $startDate -and $lastWritten -le $endDate ) ` 332 | -or ( $notin -and ( $lastWritten -lt $startDate -or $lastWritten -gt $endDate ) ) ) 333 | { 334 | New-Object -TypeName PSCustomObject -Property ( @{ 'Key' = $thisKey ; 'Last Written' = $lastwritten.ToString($dateFormat) } ) 335 | } 336 | } 337 | } 338 | } ) 339 | 340 | [string]$message = ( "Got $($results.Count) results from {0} to {1} for {2}" -f $startDate.ToString('G') ,$endDate.ToString($dateFormat) , $key ) 341 | 342 | Write-Verbose $message 343 | 344 | if( $results -and $results.Count ) 345 | { 346 | if( ! [string]::IsNullOrEmpty( $outputFile ) ) 347 | { 348 | $results | Export-Csv -Path $outputFile -NoTypeInformation -NoClobber 349 | } 350 | elseif( $gridView ) 351 | { 352 | $results | Out-GridView -Title $message 353 | } 354 | else 355 | { 356 | $results | ForEach-Object ` 357 | { 358 | "{0}{1}{2}" -f $_.Key , $delimiter , $_.'Last Written' 359 | } 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /Send to Viewer.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 4 2 | 3 | <# 4 | Simple text viewer so won't add history anywhere. Takes multiple files, from explorer send to, or if no files, uses clipboard 5 | 6 | @guyrleech 04/12/2020 7 | 8 | Modification History 9 | 10 | 06/12/2020 GRL Tidy up & optimisation 11 | 07/12/2020 GRL Stopped windows being topmost 12 | 07/12/2020 GRL Check type of data in clipboard if not text 13 | 05/11/2022 GRL Added dark mode and search 14 | 15/05/2024 GRL Changed ReadAllLines to ReadLines, added file list to output when clipboard contains file drop list 15 | 16/05/2024 GRL Change window title if parent is explorer to help identify window if left open 16 | 17 | TODO colour parameters, font, size via reg value 18 | #> 19 | 20 | [CmdletBinding()] 21 | 22 | [string]$mainwindowXAML = @' 23 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | '@ 42 | 43 | [string]$searchWindowXAML = @' 44 | 52 | 53 |