PSDev.psm1
#Region '.\prefix.ps1' 0 # The content of this file will be prepended to the top of the psm1 module file. This is useful for custom module setup is needed on import. #EndRegion '.\prefix.ps1' 2 #Region '.\Private\Assert-FolderExist.ps1' 0 function Assert-FolderExist { <# .SYNOPSIS Verify and create folder .DESCRIPTION Verifies that a folder path exists, if not it will create it .PARAMETER Path Defines the path to be validated .EXAMPLE 'C:\Temp' | Assert-FolderExist This will verify that the path exists and if it does not the folder will be created #> [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)] [string] $Path ) process { $exists = Test-Path -Path $Path -PathType Container if (!$exists) { $null = New-Item -Path $Path -ItemType Directory } } } #EndRegion '.\Private\Assert-FolderExist.ps1' 31 #Region '.\Private\Invoke-GarbageCollect.ps1' 0 function Invoke-GarbageCollect { <# .SYNOPSIS Calls system.gc collect method. Purpose is mainly for readability. .DESCRIPTION Calls system.gc collect method. Purpose is mainly for readability. .EXAMPLE Invoke-GarbageCollect #> [system.gc]::Collect() } #EndRegion '.\Private\Invoke-GarbageCollect.ps1' 13 #Region '.\Private\pslog.ps1' 0 function pslog { <# .SYNOPSIS This is simple logging function that automatically log to file. Logging to console is maintained. .DESCRIPTION This is simple logging function that automatically log to file. Logging to console is maintained. .PARAMETER Severity Defines the type of log, valid vales are, Success,Info,Warning,Error,Verbose,Debug .PARAMETER Message Defines the message for the log entry .PARAMETER Source Defines a source, this is useful to separate log entries in categories for different stages of a process or for each function, defaults to default .PARAMETER Throw Specifies that when using severity error pslog will throw. This is useful in catch statements so that the terminating error is propagated upwards in the stack. .PARAMETER LogDirectoryOverride Defines a hardcoded log directory to write the log file to. This defaults to %appdatalocal%\<modulename\logs. .PARAMETER DoNotLogToConsole Specifies that logs should only be written to the log file and not to the console. .EXAMPLE pslog Verbose 'Successfully wrote to logfile' Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Sole purpose of function is logging, including console')] [cmdletbinding()] param( [parameter(Position = 0)] [ValidateSet('Success', 'Info', 'Warning', 'Error', 'Verbose', 'Debug')] [Alias('Type')] [string] $Severity, [parameter(Mandatory, Position = 1)] [string] $Message, [parameter(position = 2)] [string] $source = 'default', [parameter(Position = 3)] [switch] $Throw, [parameter(Position = 4)] [string] $LogDirectoryOverride, [parameter(Position = 5)] [switch] $DoNotLogToConsole ) begin { if (-not $LogDirectoryOverride) { $localappdatapath = [Environment]::GetFolderPath('localapplicationdata') # ie C:\Users\<username>\AppData\Local $modulename = $MyInvocation.MyCommand.Module $logdir = "$localappdatapath\$modulename\logs" } else { $logdir = $LogDirectoryOverride } $logdir | Assert-FolderExist -Verbose:$VerbosePreference $timestamp = (Get-Date) $logfilename = ('{0}.log' -f $timestamp.ToString('yyy-MM-dd')) $timestampstring = $timestamp.ToString('yyyy-MM-ddThh:mm:ss.ffffzzz') } process { switch ($Severity) { 'Success' { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false if (-not $DoNotLogToConsole) { Write-Host -Object "SUCCESS: $timestampstring`t$source`t$message" -ForegroundColor Green } } 'Info' { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false if (-not $DoNotLogToConsole) { Write-Information -MessageData "$timestampstring`t$source`t$message" } } 'Warning' { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false if (-not $DoNotLogToConsole) { Write-Warning -Message "$timestampstring`t$source`t$message" } } 'Error' { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false if (-not $DoNotLogToConsole) { Write-Error -Message "$timestampstring`t$source`t$message" } if ($throw) { throw } } 'Verbose' { if ($VerbosePreference -ne 'SilentlyContinue') { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false } if (-not $DoNotLogToConsole) { Write-Verbose -Message "$timestampstring`t$source`t$message" } } 'Debug' { if ($DebugPreference -ne 'SilentlyContinue') { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false } if (-not $DoNotLogToConsole) { Write-Debug -Message "$timestampstring`t$source`t$message" } } } } } #EndRegion '.\Private\pslog.ps1' 137 #Region '.\Private\Write-PSProgress.ps1' 0 function Write-PSProgress { <# .SYNOPSIS Wrapper for PSProgress .DESCRIPTION This function will automatically calculate items/sec, eta, time remaining as well as set the update frequency in case the there are a lot of items processing fast. .PARAMETER Activity Defines the activity name for the progressbar .PARAMETER Id Defines a unique ID for this progressbar, this is used when nesting progressbars .PARAMETER Target Defines a arbitrary text for the currently processed item .PARAMETER ParentId Defines the ID of a parent progress bar .PARAMETER Completed Explicitly tells powershell to set the progress bar as completed removing it from view. In some cases the progress bar will linger if this is not done. .PARAMETER Counter The currently processed items counter .PARAMETER Total The total number of items to process .PARAMETER StartTime Sets the start datetime for the progressbar, this is required to calculate items/sec, eta and time remaining .PARAMETER DisableDynamicUpdateFrquency Disables the dynamic update frequency function and every item will update the status of the progressbar .PARAMETER NoTimeStats Disables calculation of items/sec, eta and time remaining .EXAMPLE 1..10000 | foreach-object -begin {$StartTime = Get-Date} -process { Write-PSProgress -Activity 'Looping' -Target $PSItem -Counter $PSItem -Total 10000 -StartTime $StartTime } Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines #> [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'Standard')] [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'Completed')] [string] $Activity, [Parameter(Position = 1, ParameterSetName = 'Standard')] [Parameter(Position = 1, ParameterSetName = 'Completed')] [ValidateRange(0, 2147483647)] [int] $Id, [Parameter(Position = 2, ParameterSetName = 'Standard')] [string] $Target, [Parameter(Position = 3, ParameterSetName = 'Standard')] [Parameter(Position = 3, ParameterSetName = 'Completed')] [ValidateRange(-1, 2147483647)] [int] $ParentId, [Parameter(Position = 4, ParameterSetname = 'Completed')] [switch] $Completed, [Parameter(Mandatory = $true, Position = 5, ParameterSetName = 'Standard')] [long] $Counter, [Parameter(Mandatory = $true, Position = 6, ParameterSetName = 'Standard')] [long] $Total, [Parameter(Position = 7, ParameterSetName = 'Standard')] [datetime] $StartTime, [Parameter(Position = 8, ParameterSetName = 'Standard')] [switch] $DisableDynamicUpdateFrquency, [Parameter(Position = 9, ParameterSetName = 'Standard')] [switch] $NoTimeStats ) # Define current timestamp $TimeStamp = (Get-Date) # Define a dynamic variable name for the global starttime variable $StartTimeVariableName = ('ProgressStartTime_{0}' -f $Activity.Replace(' ', '')) # Manage global start time variable if ($PSBoundParameters.ContainsKey('Completed') -and (Get-Variable -Name $StartTimeVariableName -Scope Global -ErrorAction SilentlyContinue)) { # Remove the global starttime variable if the Completed switch parameter is users try { Remove-Variable -Name $StartTimeVariableName -ErrorAction Stop -Scope Global } catch { throw $_ } } elseif (-not (Get-Variable -Name $StartTimeVariableName -Scope Global -ErrorAction SilentlyContinue)) { # Global variable do not exist, create global variable if ($null -eq $StartTime) { # No start time defined with parameter, use current timestamp as starttime Set-Variable -Name $StartTimeVariableName -Value $TimeStamp -Scope Global $StartTime = $TimeStamp } else { # Start time defined with parameter, use that value as starttime Set-Variable -Name $StartTimeVariableName -Value $StartTime -Scope Global } } else { # Global start time variable is defined, collect and use it $StartTime = Get-Variable -Name $StartTimeVariableName -Scope Global -ErrorAction Stop -ValueOnly } # Define frequency threshold $Frequency = [Math]::Ceiling($Total / 100) switch ($PSCmdlet.ParameterSetName) { 'Standard' { # Only update progress is any of the following is true # - DynamicUpdateFrequency is disabled # - Counter matches a mod of defined frequecy # - Counter is 0 # - Counter is equal to Total (completed) if (($DisableDynamicUpdateFrquency) -or ($Counter % $Frequency -eq 0) -or ($Counter -eq 1) -or ($Counter -eq $Total)) { # Calculations for both timestats and without $Percent = [Math]::Round(($Counter / $Total * 100), 0) # Define count progress string status $CountProgress = ('{0}/{1}' -f $Counter, $Total) # If percent would turn out to be more than 100 due to incorrect total assignment revert back to 100% to avoid that write-progress throws if ($Percent -gt 100) { $Percent = 100 } # Define write-progress splat hash $WriteProgressSplat = @{ Activity = $Activity PercentComplete = $Percent CurrentOperation = $Target } # Add ID if specified if ($Id) { $WriteProgressSplat.Id = $Id } # Add ParentID if specified if ($ParentId) { $WriteProgressSplat.ParentId = $ParentId } # Calculations for either timestats and without if ($NoTimeStats) { $WriteProgressSplat.Status = ('{0} - {1}%' -f $CountProgress, $Percent) } else { # Total seconds elapsed since start $TotalSeconds = ($TimeStamp - $StartTime).TotalSeconds # Calculate items per sec processed (IpS) $ItemsPerSecond = ([Math]::Round(($Counter / $TotalSeconds), 2)) # Calculate seconds spent per processed item (for ETA) $SecondsPerItem = if ($Counter -eq 0) { 0 } else { ($TotalSeconds / $Counter) } # Calculate seconds remainging $SecondsRemaing = ($Total - $Counter) * $SecondsPerItem $WriteProgressSplat.SecondsRemaining = $SecondsRemaing # Calculate ETA $ETA = $(($Timestamp).AddSeconds($SecondsRemaing).ToShortTimeString()) # Add findings to write-progress splat hash $WriteProgressSplat.Status = ('{0} - {1}% - ETA: {2} - IpS {3}' -f $CountProgress, $Percent, $ETA, $ItemsPerSecond) } # Call writeprogress Write-Progress @WriteProgressSplat } } 'Completed' { Write-Progress -Activity $Activity -Id $Id -Completed } } } #EndRegion '.\Private\Write-PSProgress.ps1' 214 #Region '.\Public\Add-NumberFormater.ps1' 0 function Add-NumberFormater { <# .DESCRIPTION Adding formater capabilities by overwriting the ToString method of the input double value .PARAMETER InputObject Defines the input value to process .PARAMETER Type Defines what type of value it is and what units to use. Available values is Standard and DataSize .EXAMPLE Add-NumberFormater -InputObject 2138476234 -Type DataSize Processes the number 2138476234 and returns the value with the replaced ToString() method. This case would return "1,99 GB" #> [CmdletBinding()] # Enabled to support verbose [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'Parameter use is not correctly identified by PSScriptAnalyzer')] param( [Parameter(Mandatory, ValueFromPipeline)][Alias('Double', 'Number')][double[]]$InputObject, [ValidateSet('DataSize', 'Standard')][string]$Type = 'Standard' ) begin { $Configuration = @{ DataSize = @{ Base = 1024 Units = @('', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB') } Standard = @{ Base = 1000 Units = @('', 'K', 'MN', 'MD', 'BN', 'BD', 'TN', 'TD') } } } process { $InputObject | foreach-object { $CurrentNumber = $_ $TempCopyOfCurrentNumber = $CurrentNumber if ($TempCopyOfCurrentNumber -lt $Configuration.($Type).Base) { $DisplayString = "'{0:N}'" -f [double]($TempCopyOfCurrentNumber) } else { $i = 0 while ($TempCopyOfCurrentNumber -ge $Configuration.($Type).Base -and $i -lt $Configuration.($Type).Units.Length - 1 ) { $TempCopyOfCurrentNumber /= $Configuration.($Type).Base $i++ } $DisplayString = "'{0:N2} {1}'" -f [double]($TempCopyOfCurrentNumber), ($Configuration.($Type).Units[$i]) } $NewObject = $CurrentNumber | Add-Member -MemberType ScriptMethod -Name ToString -Value ([Scriptblock]::Create($DisplayString)) -Force -PassThru return $NewObject } } end { } } #EndRegion '.\Public\Add-NumberFormater.ps1' 56 #Region '.\Public\Convert-Object.ps1' 0 function Convert-Object { <# .DESCRIPTION Function that converts a specific input value to a number of output formats. This is a function that allows shortcuts of already existing powershell/.net features. All conversions are made with an intermediate conversion to byte[]. .PARAMETER FromString Defines the input as a standard string, ie "Hello World" .PARAMETER FromBase64String Defines the input as a base64 encoded string, ie "MjUzZmY1NWUtNzhjNy00MjczLWFmYmUtNjgzZThiZjZiMWE1" .PARAMETER FromGUID Defines the input as a GUID value represented in string format, ie "253ff55e-78c7-4273-afbe-683e8bf6b1a5" .PARAMETER FromHexString Defines the input as a Hex value represented in string format, ie "48656c6c6f20576f726c64" .PARAMETER FromIntArray Defines the input as a int array, ie 1,2,3 .PARAMETER FromInt Defines the input as a int value, ie 12345 .PARAMETER FromCharArray Defines the input as a char array, ie 'a','b','c' .PARAMETER FromByteArray Defines the input as a byte array, ie 72,101,108,108,111,32,87,111,114,108,100 .PARAMETER FromScriptBlock Defines the input as a scriptblock, ie {Write-Host 'Test'} .PARAMETER FromSecureString Deinfes the input as a securestring, ie 01000000d08c9ddf0115d1118c7a00c04fc2.... .PARAMETER FromSecureStringObject Defines the input as a securestringobject, ie (Get-Credential) .PARAMETER FromBinaryStringArray Defines the input as a binarystring array, ie '01001000','01100101','01101100','01101100','01101111' .PARAMETER FromIPAddressString Defines the input as a ip address, ie 72.101.108.108 .PARAMETER FromByteCollection Defines the input as an byte collection .PARAMETER Properties Defines a string array with properties to return. Defaults to the reserved word 'All' .EXAMPLE Code Description #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "")] [CmdletBinding(DefaultParameterSetName = 'FromString')] param() DynamicParam { $Conversions = @( 'String', 'Base64String', 'ByteArray', 'CharArray', 'GUID', 'HexStringArray', 'HexString', 'SecureString', 'SecureStringObject', 'BinaryStringArray', 'BigInteger', 'Int64', 'IPAddressString', 'ScriptBlock', 'ByteCollection' ) # Property parameter # Define base parameter attributes $ParameterName = 'Property' $ParameterDataType = [string] $ParameterAlias = @('Properties') $ParameterValidateSet = $Conversions # Create simple parameter attributes $ParameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute $ParameterAttribute.ParameterSetName = '__AllParameterSets' # Create validateset attribute $ValidateSetAttribute = New-Object -TypeName System.Management.Automation.ValidateSetAttribute -ArgumentList $ParameterValidateSet # Create alias attribute $AliasAttribute = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList $ParameterAlias $AttributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] $AttributeCollection.Add($ParameterAttribute) $AttributeCollection.Add($ValidateSetAttribute) $AttributeCollection.Add($AliasAttribute) # Define Dynamic parameter based on attribute collection $DynamicParameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList ($ParameterName, $ParameterDataType, $AttributeCollection) # If paramDictionary already exists, add dynamic parameter to dictionary, otherwise create a new dictionary if ($paramDictionary -and $paramDictionary.Keys -notcontains $ParameterName) { $paramDictionary.Add($ParameterName, $DynamicParameter) } else { $paramDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary $paramDictionary.Add($ParameterName, $DynamicParameter) } # Fromparameters foreach ($source in $Conversions) { # Define base parameter attributes $ParameterName = ('From{0}' -f $Source) $ParameterDataType = [Object] # Create simple parameter attributes $ParameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute $ParameterAttribute.ParameterSetName = $ParameterName $ParameterAttribute.Mandatory = $true # Sätt samman attribut-komponenter till collection $AttributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] $AttributeCollection.Add($ParameterAttribute) # Define Dynamic parameter based on attribute collection $DynamicParameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList ($ParameterName, $ParameterDataType, $AttributeCollection) # If paramDictionary already exists, add dynamic parameter to dictionary, otherwise create a new dictionary if ($paramDictionary -and $paramDictionary.Keys -notcontains $ParameterName) { $paramDictionary.Add($ParameterName, $DynamicParameter) } else { $paramDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary $paramDictionary.Add($ParameterName, $DynamicParameter) } } return $paramDictionary } PROCESS { Remove-Variable -Name 'Master' -ErrorAction SilentlyContinue #region Source $FromSelection = (($PSBoundParameters).keys.where( {$_ -ne 'Property'})[0]) $FromValue = $PSBoundParameters[$FromSelection] $Master = switch ($FromSelection) { 'FromString' {[byte[]](([Text.Encoding]::UTF8).GetBytes($FromValue))} 'FromBase64String' {Convert-Object -FromCharArray ([convert]::FromBase64String($FromValue)) -Property ByteArray} 'FromByteArray' {$FromValue} 'FromCharArray' {[byte[]]$FromValue} 'FromGUID' {$FromValue.ToByteArray()} 'FromHexStringArray' {Convert-Object -FromHexString ($FromValue -join '') -Property ByteArray} 'FromHexString' { $Bytes = [byte[]]::new($FromValue.Length / 2) For ($i = 0; $i -lt $FromValue.Length; $i += 2) { $Bytes[$i / 2] = [convert]::ToByte($FromValue.Substring($i, 2), 16) } $Bytes } 'FromSecureString' {Convert-Object -FromSecureStringObject (ConvertTo-SecureString -String $FromValue) -Property ByteArray} 'FromSecureStringObject' { $marshal = [Runtime.InteropServices.Marshal] $Pointer = $marshal::SecureStringToBSTR($FromValue) Convert-Object -FromString ($marshal::PtrToStringBSTR($Pointer)) -Property ByteArray $marshal::ZeroFreeBSTR($Pointer) } 'FromBinaryStringArray' {[byte[]]($FromValue | foreach-object {[convert]::ToByte($_, 2)})} 'FromBigInteger' {([System.Numerics.BigInteger]$FromValue).ToByteArray()} 'FromInt64' {[System.BitConverter]::GetBytes($FromValue)} 'FromIPAddressString' {Convert-Object -FromCharArray ($FromValue.Split('.') | ForEach-Object {[convert]::ToInt64($_)}) -Properties ByteArray} 'FromScriptblock' {Convert-Object -FromString $FromValue.ToString() -Property ByteArray} 'FromByteCollection' {$FromValue.Bytes} } #region Target switch ($PSBoundParameters['Property']) { 'String' {[Text.Encoding]::UTF8.GetString($Master)} 'Base64String' {[convert]::ToBase64String($Master)} 'ByteArray' {$Master} 'CharArray' {[char[]]$Master} 'GUID' {try {[guid]::new((Convert-Object -FromByteArray $Master -Property String))} catch {'N/A'}} 'HexStringArray' {ForEach ($byte in $Master) {("{0:x2}" -f $byte)}} 'HexString' {(Convert-Object -FromByteArray $Master -Property HexStringArray) -join ''} 'SecureString' {(ConvertTo-SecureString -String (Convert-Object -FromByteArray $Master -Property String) -AsPlainText -Force | ConvertFrom-SecureString)} 'SecureStringObject' {(ConvertTo-SecureString -String (Convert-Object -FromByteArray $Master -Properties String) -AsPlainText -Force)} 'BinaryStringArray' {$Master | foreach-object {[convert]::ToString($_, 2).PadLeft(8, '0')}} 'BigInteger' {[bigint]::New(($Master += [byte]0))} 'Int64' {if ($Master.Length -eq 8) {[BitConverter]::ToInt64($Master, 0)} else {'N/A'}} 'IPAddressString' {$Master -join '.'} 'ScriptBlock' {try {[scriptblock]::Create((Convert-Object -FromByteArray $Master -Properties String))} catch {[scriptblock]::Create('N/A')}} 'ByteCollection' {[Microsoft.PowerShell.Commands.ByteCollection]::New($Master)} default { $Hash = [ordered]@{} foreach ($Target in $Conversions) { $Hash.$Target = Convert-Object -FromByteArray $Master -Property $Target } [pscustomobject]$Hash } } } } #EndRegion '.\Public\Convert-Object.ps1' 190 #Region '.\Public\Debug-String.ps1' 0 <# Prerequisites: PowerShell v5.1 and above (verified; may also work in earlier versions) License: MIT Author: Michael Klement (email redacted to spare author of spam-hunter-bots.) #> function Debug-String { <# .SYNOPSIS Outputs a string in diagnostic form or as source code. .DESCRIPTION Author: Michael Klement Prints a string with control or hidden characters visualized, and optionally all non-ASCII-range Unicode characters represented as escape sequences. With -AsSourceCode, the result is printed in single-line form as a double-quoted PowerShell string literal that is reusable as source code, Common control characters are visualized using PowerShell's own escaping notation by default, such as "`t" for a tab, "`r" for a CR, but a LF is visualized as itself, as an actual newline, unless you specify -SingleLine. As an alternative, if you want ASCII-range control characters visualized in caret notation (see https://en.wikipedia.org/wiki/Caret_notation), similar to cat -A on Linux, use -CaretNotation. E.g., ^M then represents a CR; but note that a LF is always represented as "$" followed by an actual newline. Any other control characters as well as otherwise hidden characters or format / punctuation characters in the non-ASCII range are represented in `u{hex-code-point} notation. To print space characters as themselves, use -NoSpacesAsDots. $null inputs are accepted, but a warning is issued. .PARAMETER InputObject Defines the string to analyze .PARAMETER CaretNotation Causes LF to be visualized as "$" and all other ASCII-range control characters in caret notation, similar to `cat -A` on Linux. .PARAMETER Delimiters You may optionally specify delimiters that the visualization of each input string is enclosed in as a a whole its boundaries. You may specify a single string or a 2-element array. .PARAMETER NoSpacesAsDots By default, space chars. are visualized as "·", the MIDDLE DOT char. (U+00B7) Use this switch to represent spaces as themselves. .PARAMETER NoEmphasis By default, those characters (other than spaces) that aren't output as themselves, i.e. control characters and, if requested with -UnicodeEscapes, non-ASCII-range characters, are highlighted by color inversion, using ANSI (VT) escape sequences. Use this switch to turn off this highlighting. Note that if $PSStyle.OutputRendering = 'PlainText' is in effect, the highlighting isn't *shown* even *without* -NoEmphasis, but the escape sequences are still part of the output string. Only -NoEmphasis prevents inclusion of these escape sequences. .PARAMETER AsSourceCode Outputs each input string as a double-quoted PowerShell string that is reusable in source code, with embedded double quotes, backticks, and "$" signs backtick-escaped. Use -SingleLine to get a single-line representation. Control characters that have no native PS escape sequence are represented using `u{<hex-code-point} notation, which will only work in PowerShell *Core* (v6+) source code. .PARAMETER SingleLine Requests a single-line representation, where LF characters are represented as `n instead of actual line breaks. .PARAMETER UnicodeEscapes Requests that all non-ASCII-range characters - such as accented letters - in the input string be represented as Unicode escape sequences in the form `u{hex-code-point}. Whe cominbed with -AsSourceCode, the result is a PowerShell string literal composed of ASCII-range characters only, but note that only PowerShell *Core* (v6+) understands such Unicode escapes. By default, only control characters that don't have a native PS escape sequence / cannot be represented with caret notation are represented this way. .EXAMPLE PS> "a`ab`t c`0d`r`n" | Debug-String -Delimiters [, ] [a`0b`t·c`0d`r` ] .EXAMPLE PS> "a`ab`t c`0d`r`n" | Debug-String -CaretNotation a^Gb^I c^@d^M$ .EXAMPLE PS> "a-ü`u{2028}" | Debug-String -UnicodeEscapes # The dash is an em-dash (U+2014) a·`u{2014}·`u{fc} .EXAMPLE PS> "a`ab`t c`0d`r`n" | Debug-String -AsSourceCode -SingleLine # roundtrip "a`ab`t c`0d`r`n" #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'NoSpacesAsDots', Justification = 'False positive')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'NoEmphasis', Justification = 'False positive')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'SingleLine', Justification = 'False positive')] [CmdletBinding(DefaultParameterSetName = 'Standard', PositionalBinding = $false)] param( [Parameter(ValueFromPipeline, Mandatory, ParameterSetName = 'Standard', Position = 0)] [Parameter(ValueFromPipeline, Mandatory, ParameterSetName = 'Caret', Position = 0)] [Parameter(ValueFromPipeline, Mandatory, ParameterSetName = 'AsSourceCode', Position = 0)] [AllowNull()] [object[]] $InputObject, [Parameter(ParameterSetName = 'Standard')] [Parameter(ParameterSetName = 'Caret')] [string[]] $Delimiters, # for enclosing the visualized strings as a whole - probably rarely used. [Parameter(ParameterSetName = 'Caret')] [switch] $CaretNotation, [Parameter(ParameterSetName = 'Standard')] [Parameter(ParameterSetName = 'Caret')] [switch] $NoSpacesAsDots, [Parameter(ParameterSetName = 'Caret')] [Parameter(ParameterSetName = 'Standard')] [switch] $NoEmphasis, [Parameter(ParameterSetName = 'AsSourceCode')] [switch] $AsSourceCode, [Parameter(ParameterSetName = 'Standard')] [Parameter(ParameterSetName = 'AsSourceCode')] [switch] $SingleLine, [Parameter(ParameterSetName = 'Standard')] [Parameter(ParameterSetName = 'Caret')] [Parameter(ParameterSetName = 'AsSourceCode')] [switch] $UnicodeEscapes ) begin { $esc = [char] 0x1b if ($UnicodeEscapes) { $re = [regex] '(?s).' # We must look at *all* characters. } else { # Only control / separator / punctuation chars. # * \p{C} matches any Unicode control / format/ invisible characters, both inside and outside # the ASCII range; note that tabs (`t) are control character too, but not spaces; it comprises # the following Unicode categories: Control, Format, Private_Use, Surrogate, Unassigned # * \p{P} comprises punctuation characters. # * \p{Z} comprises separator chars., including spaces, but not other ASCII whitespace, which is in the Control category. # Note: For -AsSourceCode we include ` (backticks) too. $re = if ($AsSourceCode) { [regex] '[`\p{C}\p{P}\p{Z}]' } else { [regex] '[\p{C}\p{P}\p{Z}]' } } $openingDelim = $closingDelim = '' if ($Delimiters) { $openingDelim = $Delimiters[0] $closingDelim = $Delimiters[1] if (-not $closingDelim) { $closingDelim = $openingDelim } } } process { if ($null -eq $InputObject) { Write-Warning 'Ignoring $null input.'; return } foreach ($str in $InputObject) { if ($null -eq $str) { Write-Warning 'Ignoring $null input.'; continue } if ($str -isnot [string]) { $str = -join ($str | Out-String -Stream) } $strViz = $re.Replace($str, { param($match) $char = [char] $match.Value[0] $codePoint = [uint16] $char $sbToUnicodeEscape = { '`u{' + '{0:x}' -f [int] $Args[0] + '}' } # wv -v ('in [{0}]' -f [char] $match.Value) $vizChar = if ($CaretNotation) { if ($codePoint -eq 0xA) { # LF -> $<newline> '$' + $char } elseif ($codePoint -eq 0x20) { # space char. if ($NoSpacesAsDots) { ' ' } else { '·' } } elseif ($codePoint -ge 0 -and $codePoint -le 31 -or $codePoint -eq 127) { # If it's a control character in the ASCII range, # use caret notation too (C0 range). # See https://en.wikipedia.org/wiki/Caret_notation '^' + [char] ((64 + $codePoint) -band 0x7f) } elseif ($codePoint -ge 128) { # Non-ASCII (control) character -> `u{<hex-code-point>} & $sbToUnicodeEscape $codePoint } else { $char } } else { # -not $CaretNotation # Translate control chars. that have native PS escape sequences # into these escape sequences. switch ($codePoint) { 0 { '`0'; break } 7 { '`a'; break } 8 { '`b'; break } 9 { '`t'; break } 11 { '`v'; break } 12 { '`f'; break } 10 { if ($SingleLine) { '`n' } else { "`n" }; break } 13 { '`r'; break } 27 { '`e'; break } 32 { if ($AsSourceCode -or $NoSpacesAsDots) { ' ' } else { '·' }; break } # Spaces are visualized as middle dots by default. default { # Note: 0x7f (DELETE) is technically still in the ASCII range, but it is a control char. that should be visualized as such # (and has no dedicated escape sequence). if ($codePoint -ge 0x7f) { & $sbToUnicodeEscape $codePoint } elseif ($AsSourceCode -and $codePoint -eq 0x60) { # ` (backtick) '``' } else { $char } } } # switch } # Return the visualized character. if (-not ($NoEmphasis -or $AsSourceCode) -and $char -ne ' ' -and $vizChar -cne $char) { # Highlight a visualized character that isn't visualized as itself (apart from spaces) # by inverting its colors, using VT / ANSI escape sequences "$esc[7m$vizChar$esc[m" } else { $vizChar } }) # .Replace # Output if ($AsSourceCode) { '"{0}"' -f ($strViz -replace '"', '`"' -replace '\$', '`$') } else { if ($CaretNotation) { # If a string *ended* in a newline, our visualization now has # a trailing LF, which we remove. $strViz = $strViz -replace '(?s)^(.*\$)\n$', '$1' } $openingDelim + $strViz + $closingDelim } } } # process } # function #EndRegion '.\Public\Debug-String.ps1' 362 #Region '.\Public\Get-Color.ps1' 0 function Get-Color { <# .DESCRIPTION Functions showing all consolecolors .EXAMPLE Get-Color Shows all the available consolecolors #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Interactive command')] [CmdletBinding()] param() [enum]::GetNames([consolecolor]) | ForEach-Object { Write-Host (' {0}' -f $_).PadRight(15) -BackgroundColor $_ -NoNewline Write-Host ' ' -NoNewline Write-Host ('{0} ' -f $_).PadLeft(15) -BackgroundColor $_ -ForegroundColor Black } } #EndRegion '.\Public\Get-Color.ps1' 20 #Region '.\Public\Get-DotNetVersion.ps1' 0 function Get-DotNetVersion { <# .DESCRIPTION Script retreivs the .net framework version from the registry .PARAMETER Release Defines the release version .EXAMPLE Get-DotNetVersion Script retreivs the .net framework version from the registry #> [CmdletBinding()] param( [int]$Release = '' ) if ($Release -eq '') { $Release = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full' | Select-Object -ExpandProperty Release } $RegKey = [hashtable][ordered]@{ VersionNumber = $Release Version = '' OS = '' } switch ($RegKey.VersionNumber) { 378389 { $RegKey.Version = '.NET Framework 4.5.0' $RegKey.OS = 'All' } 378675 { $RegKey.Version = '.NET Framework 4.5.1' $RegKey.OS = 'Windows 8.1' } 378758 { $RegKey.Version = '.NET Framework 4.5.1' $RegKey.OS = 'All other than Windows 8.1' } 379893 { $RegKey.Version = '.NET Framework 4.5.2' $RegKey.OS = 'All' } 393295 { $RegKey.Version = '.NET Framework 4.6.0' $RegKey.OS = 'Windows 10' } 393297 { $RegKey.Version = '.NET Framework 4.6.0' $RegKey.OS = 'All other than Windows 10' } 394254 { $RegKey.Version = '.NET Framework 4.6.1' $RegKey.OS = 'Windows 10 November Update' } 394271 { $RegKey.Version = '.NET Framework 4.6.1' $RegKey.OS = 'All other than Windows 10 November Update' } 394802 { $RegKey.Version = '.NET Framework 4.6.2' $RegKey.OS = 'Windows 10 Anniversary Update and Windows Server 2016' } 394806 { $RegKey.Version = '.NET Framework 4.6.2' $RegKey.OS = 'All except Windows 10 Anniversary Update and Windows Server 2016' } 460798 { $RegKey.Version = '.NET Framework 4.7.0' $RegKey.OS = 'Windows 10 Creators Update' } 460805 { $RegKey.Version = '.NET Framework 4.7.0' $RegKey.OS = 'All except Windows 10 Creators Update' } 461308 { $RegKey.Version = '.NET Framework 4.7.1' $RegKey.OS = 'Windows 10 Creators Update and Windows Server, version 1709' } 461310 { $RegKey.Version = '.NET Framework 4.7.1' $RegKey.OS = 'All except Windows 10 Creators Update and Windows Server, version 1709' } 461808 { $RegKey.Version = '.NET Framework 4.7.2' $RegKey.OS = 'Windows 10 April 2018 Update and Windows Server, version 1803' } 461814 { $RegKey.Version = '.NET Framework 4.7.2' $RegKey.OS = 'All except [Windows 10 April 2018 Update] and [Windows Server, version 1803]' } 528449 { $RegKey.Version = '.NET Framework 4.8' $RegKey.OS = 'Windows 11 and Windows Server 2022' } 528040 { $RegKey.Version = '.NET Framework 4.8' $RegKey.OS = 'Windows 10 May 2019 Update and Windows 10 November 2019 Update' } 528372 { $RegKey.Version = '.NET Framework 4.8' $RegKey.OS = 'Windows 10 May 2020 Update and Windows 10 October 2020 Update and Windows 10 May 2021 Update' } 528049 { $RegKey.Version = '.NET Framework 4.8' $RegKey.OS = 'All except [Windows 11],[Windows Server 2022],[Windows 10 May 2020 Update],[Windows 10 October 2020 Update],[Windows 10 May 2021 Update],[Windows 10 May 2019 Update],[Windows 10 November 2019 Update]' } 533325 { $RegKey.Version = '.NET Framework 4.8.1' $RegKey.OS = 'All' } default { $RegKey.Version = '<Unknown>' } } Write-Output (New-Object -TypeName PSObject -Property $RegKey) } #EndRegion '.\Public\Get-DotNetVersion.ps1' 144 #Region '.\Public\Get-Office365IPURL.ps1' 0 function Get-Office365IPURL { <# .DESCRIPTION Retreive a list of ip and urls required for communication to and from Office 365. .PARAMETER Services Defines which services to retreive IP and URLs for. Valid values are Skype,Exchange,Sharepoint. Note that Teams is included in the Skype ruleset and OneDrive is included in the Sharepoint ruleset. .PARAMETER OnlyRequired Defines that only rules that are required are returned. This will exclude optional optimize rules. .PARAMETER Types Defines what type of rules to return. Valid values are URL,IP4,IP6 .PARAMETER OutputFormat Defines the output format, defaults to an array of objects. Valid values are Object and JSON as of now. If a specific format is needed for a firewall please raise a issue with the instructions for the format and it is possible to create preset for it. .PARAMETER Office365IPURL Defines the URL to the Office 365 IP URL Endpoint. Defaults to 'https://endpoints.office.com/endpoints/worldwide?clientrequestid=b10c5ed1-bad1-445f-b386-b919946339a7'. Provided as parameter to allow queries to other environments than worldwide as well as keep agility if Microsoft would change URL. .EXAMPLE Get-Office365IPURL -Services Exchange,Skype -OnlyRequired -Types IP4,URL -Outputformat JSON #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Services', Justification = 'False positive')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Types', Justification = 'False positive')] [CmdletBinding()] param( [Parameter()] [ValidateSet('Skype', 'Exchange', 'Sharepoint')] [string[]] $Services = @('Skype', 'Exchange', 'Sharepoint'), [Parameter()] [switch] $OnlyRequired, [Parameter()] [ValidateSet('URL', 'IP4', 'IP6')] [string[]] $Types = @('URL', 'IP4', 'IP6'), [Parameter()] [ValidateSet('Object', 'JSON')] [string] $OutputFormat = 'Object', [Parameter()] [string] $Office365IPURL = 'https://endpoints.office.com/endpoints/worldwide?clientrequestid=b10c5ed1-bad1-445f-b386-b919946339a7' ) $ErrorActionPreference = 'Stop' # Get latest IP URL info $Office365Endpoints = Invoke-RestMethod -Uri $Office365IPURL -Method Get # Import net module Import-Module indented.net.ip # Loop through rules $Result = $Office365Endpoints | Where-Object { $Services -contains $_.ServiceArea } | ForEach-Object { $CurrentRule = $PSItem $ObjectHash = [ordered]@{ Group = '' Service = $CurrentRule.ServiceArea Type = '' Protocol = '' Port = $null Endpoint = '' Required = $CurrentRule.Required } $CurrentRule.URLs | Where-Object { $_ -ne '' -and $_ -ne $null } | ForEach-Object { $ObjectHash.Type = 'URL' $ObjectHash.Endpoint = $PSItem $CurrentRule.TCPPorts -split (',') | Where-Object { $_ -ne '' } | ForEach-Object { $ObjectHash.Protocol = 'TCP' $ObjectHash.Port = $PSItem $ObjectHash.Group = $CurrentRule.ServiceArea + '_' + 'TCP' + '_' + "$PSItem" + '_' + 'URL' [pscustomobject]$ObjectHash } $CurrentRule.UDPPorts -split (',') | Where-Object { $_ -ne '' } | ForEach-Object { $ObjectHash.Protocol = 'UDP' $ObjectHash.Port = $PSItem $ObjectHash.Group = $CurrentRule.ServiceArea + '_' + 'UDP' + '_' + "$PSItem" + '_' + 'URL' [pscustomobject]$ObjectHash } } # Process IPs $CurrentRule.ips | Where-Object { $_ -ne '' -and $_ -ne $null } | ForEach-Object { if ($PSItem -like '*:*') { $ObjectHash.Type = 'IP6' } else { $ObjectHash.Type = 'IP4' } $ObjectHash.Endpoint = $PSItem $CurrentRule.TCPPorts -split (',') | Where-Object { $_ -ne '' } | ForEach-Object { $ObjectHash.Protocol = 'TCP' $ObjectHash.Port = $PSItem $ObjectHash.Group = $CurrentRule.ServiceArea + '_' + 'TCP' + '_' + "$PSItem" + '_' + 'IP' [pscustomobject]$ObjectHash } $CurrentRule.UDPPorts -split (',') | Where-Object { $_ -ne '' } | ForEach-Object { $ObjectHash.Protocol = 'UDP' $ObjectHash.Port = $PSItem $ObjectHash.Group = $CurrentRule.ServiceArea + '_' + 'UDP' + '_' + "$PSItem" + '_' + 'IP' [pscustomobject]$ObjectHash } } } | Where-Object { $Types -contains $PSItem.Type } switch ($OutputFormat) { 'Object' { if ($OnlyRequired) { $Result | Where-Object { $_.required -eq $true } | Sort-Object -Property Group | Format-Table } else { $Result | Sort-Object -Property Group | Format-Table } } 'JSON' { $JSONHash = [ordered]@{} $Result | Group-Object -Property Protocol | ForEach-Object { $CurrentProtocolGroup = $PSItem # Create protocol node if it does not exist if (-not $JSONHash.Contains($CurrentProtocolGroup.Name)) { $JSONHash.Add($CurrentProtocolGroup.Name, [ordered]@{}) } $CurrentProtocolGroup.Group | Group-Object -Property Port | ForEach-Object { $CurrentPortGroup = $PSItem # Create port node if it does not exists if (-not $JSONHash.$($CurrentProtocolGroup.Name).Contains($CurrentPortGroup.Name)) { $JSONHash.$($CurrentProtocolGroup.Name).Add($CurrentPortGroup.Name, [ordered]@{}) } $CurrentPortGroup.Group | Group-Object -Property Type | ForEach-Object { $CurrentTypeGroup = $PSItem $EndpointArray = [string[]]($CurrentTypeGroup.Group.Endpoint) $JSONHash.$($CurrentProtocolGroup.Name).$($CurrentPortGroup.Name).Add($CurrentTypeGroup.Name, $EndpointArray) } } } $JSONHash | ConvertTo-Json -Depth 10 } } } #EndRegion '.\Public\Get-Office365IPURL.ps1' 167 #Region '.\Public\Get-PublicIP.ps1' 0 function Get-PublicIP { <# .DESCRIPTION Get the current public facing IP address .PARAMETER Name Description .EXAMPLE Get-PublicIP Description of example #> [CmdletBinding()] param( ) PROCESS { Invoke-RestMethod -Uri 'http://ipinfo.io/json' } } #EndRegion '.\Public\Get-PublicIP.ps1' 19 #Region '.\Public\Get-StringHash.ps1' 0 function Get-StringHash { <# .DESCRIPTION Generates a hash of an string object .PARAMETER Strings Defines the array of strings to generate hashes of .PARAMETER Algorithm Defines which hashing algorithm to use, valid values are MD5, SHA256, SHA384 and SHA512. Defaults to SHA512 .PARAMETER Salt Defines a specific salt to use, this is useful when recalculating a string hash with a known salt for comparison. A new random salt is generated by default for every string that is processed. .PARAMETER Iterations Defines the number of rehashing operations that is performed. .PARAMETER RandomSalt Defines that a random salt should be used .EXAMPLE Get-StringHash -Strings 'ThisIsAComplicatedPassword123#' -Algorithm SHA512 Hashes the string specified #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'False positive')] [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)][String[]]$Strings, [ValidateSet('MD5', 'SHA256', 'SHA384', 'SHA512', 'SHA1')][string]$Algorithm = 'SHA256', [string]$Salt = '', [switch]$RandomSalt, [int]$Iterations = 10 ) BEGIN { if ($Iterations -eq 0) { $Iterations = 1 } } PROCESS { $Strings | ForEach-Object { # if no salt is specified, generate a new salt to use. if ($RandomSalt) { $Salt = [guid]::NewGuid().Guid } $String = $_ $StringBytes = [Text.Encoding]::UTF8.GetBytes($String) if ($Salt -ne '') { $SaltBytes = [Text.Encoding]::UTF8.GetBytes($salt) } $Hasher = [Security.Cryptography.HashAlgorithm]::Create($Algorithm) $StringBuilder = New-Object -TypeName System.Text.StringBuilder $Measure = Measure-Command -Expression { # Compute first hash if ($Salt -ne '') { $HashBytes = $Hasher.ComputeHash($StringBytes + $SaltBytes) } else { $HashBytes = $Hasher.ComputeHash($StringBytes) } # Iterate rehashing if ($Iterations -ge 2) { 2..$Iterations | ForEach-Object { if ($Salt -ne '') { $HashBytes = $Hasher.ComputeHash($HashBytes + $StringBytes + $SaltBytes) } else { $HashBytes = $Hasher.ComputeHash($HashBytes + $StringBytes) } } } } # Convert final hash to a string $HashBytes | ForEach-Object { $null = $StringBuilder.Append($_.ToString('x2')) } # Return object [pscustomobject]@{ Hash = $StringBuilder.ToString() OriginalString = $String Algorithm = $algorithm Iterations = $Iterations Salt = $salt Compute = [math]::Round($Measure.TotalMilliseconds) } } } } #EndRegion '.\Public\Get-StringHash.ps1' 99 #Region '.\Public\Group-ObjectEvenly.ps1' 0 function Group-ObjectEvenly { <# .DESCRIPTION Function that splits a object array into groups of a specific number .PARAMETER InputObject Defines the object array to split .PARAMETER SizeOfGroup Defines the size of each group of objects .PARAMETER NbrOfGroups Defines the number of groups should be created, the objects will be evenly distributed within the groups .EXAMPLE Get-Process | Group-ObjectByAmount -Amount 5 This example collects all processes running and groups them in groups of five processes per object. #> [CmdletBinding()] [OutputType([System.Collections.ArrayList])] param( [Parameter(Mandatory, ValueFromPipeline)][object[]]$InputObject, [Parameter(Mandatory, ParameterSetName = 'SizeOfGroup')][int]$SizeOfGroup, [Parameter(Mandatory, ParameterSetName = 'NbrOfGroups')][int]$NbrOfGroups ) begin { $AllObjects = [collections.arraylist]::new() $Groups = [collections.arraylist]::new() } process { $InputObject | ForEach-Object { $null = $AllObjects.Add($_) } } end { switch ($PSCmdlet.ParameterSetName) { 'SizeOfGroup' { $ID = 1 while ($AllObjects.Count -ne 0) { $Group = [pscustomobject]@{ ID = $ID Group = $AllObjects | Select-Object -First $SizeOfGroup } $null = $Groups.Add($Group) $AllObjects = $AllObjects | Select-Object -Skip $SizeOfGroup $ID++ } $Groups } 'NbrOfGroups' { $ID = 1 while ($AllObjects.Count -ne 0) { $SizeOfGroup = [Math]::Max(([Math]::Round(($AllObjects.count / $NbrOfGroups))), 1) $Group = [pscustomobject]@{ ID = $ID Group = $AllObjects | Select-Object -First $SizeOfGroup } $null = $Groups.Add($Group) $AllObjects = $AllObjects | Select-Object -Skip $SizeOfGroup $ID++ $NbrOfGroups-- } $Groups } } } } #EndRegion '.\Public\Group-ObjectEvenly.ps1' 74 #Region '.\Public\New-Password.ps1' 0 function New-Password { <# .DESCRIPTION Function that generates passwords .PARAMETER Simple Specifies that the function will create a password according to the following ruleset The first characters is a captial consonant letter The second character is a lower vowel letter The third character is a lower consonant letter The fourth character is lower vowel letter The remaining four characters are digits. This structure creates a password that is easy to remember but is less secure. What makes the password easier to remember is that the most character combinations are reasonably easy to prenounce. These password should only be used temporary. An few examples might be: Wodi6380 Jaki2830 Kezo2617 .PARAMETER Count Defines the number of passwords to generate. This can be used to create batches of passwords. Defaults to 1. .PARAMETER Length When the parameter set custom is used this parameter lets the user select how long the password should be. Defaults to 8. .PARAMETER Signs When the parameter set custom is used this parameter lets the user select how many signs/symbols that should be included in the password. Defaults to 3. .PARAMETER ReturnSecureStringObject Return password as secure string .PARAMETER Custom Defines a custom rule for password selection .PARAMETER AllowInterchangableCharacters Defines that characters as i|I and l|L and 0|O can be used in the password, defaults to false .EXAMPLE New-Password -Simple -Count 3 Wuba9710 Suve0945 Zigo1479 This example uses the method to create a password that is easy to remember but less secure. .EXAMPLE New-Password -Length 10 -Signs 5 ..&La:J%NF This example creates a password of alphanumrerical characters including five signs/symbols. .EXAMPLE New-Password -Length 20 J.rp318xVD?Twhah'K7b This example creates a password of alpanumerical+signs characters #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'No need, non-destructive change')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'FalsePositive, plain text string is generated by the script')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'False positive')] [CmdletBinding(DefaultParameterSetName = 'Simple')] param( [Parameter(ParameterSetName = 'Custom')] [ValidateRange(0, 128)][int]$Length = 12, [Parameter(ParameterSetName = 'Custom')] [int]$Signs = 3, [int]$Count = 1, [Parameter(ParameterSetName = 'Custom')] [switch]$Custom, [switch]$ReturnSecureStringObject, [switch]$AllowInterchangableCharacters ) $Arrays = @{ LettersAndDigits = [char[]]@(48..57 + 65..90 + 97..122) Signs = [char[]]@(33..35 + 37..39 + 42 + 44 + 46..47 + 58..59 + 63..64 + 92) Digits = [char[]]@(48..57) UpperConsonants = [char[]]@('B', 'C', 'D', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'X', 'Z', 'W', 'Y') LowerConsonants = [char[]]@('b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l' , 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'x', 'z', 'w', 'y') LowerVowels = [char[]]@('a', 'e', 'i', 'o', 'u') } if (-not $AllowInterchangableCharacters) { $InterchangableCharacters = @( [char]'l', [char]'I', [char]'O', [char]'o', [char]48 ) # Clone keys from hashtable so that the hashtable can be modified during enumeration $Arrays.Keys.Clone() | ForEach-Object { $CurrentArrayKey = $PSItem $Arrays[$CurrentArrayKey] = $Arrays[$CurrentArrayKey] | Where-Object { $InterchangableCharacters -notcontains $PSItem } } } if ($Signs -gt $Length) { Write-Warning ('Sign characters cannot be greater than the length, setting Signs to the specified length ({0})' -f $Length) $Signs = $Length } 1..$Count | ForEach-Object { switch ($PSCmdlet.ParameterSetName) { 'Simple' { $CharArray = [char[]]@() $CharArray += ($Arrays.UpperConsonants | Get-Random -Count 1) $CharArray += ($Arrays.LowerVowels | Get-Random -Count 1) $CharArray += ($Arrays.LowerConsonants | Get-Random -Count 1) $CharArray += ($Arrays.LowerVowels | Get-Random -Count 1) $CharArray += ($Arrays.LowerConsonants | Get-Random -Count 1) $CharArray += ($Arrays.Digits | Get-Random -Count 5) $PasswordString = $CharArray -join '' } 'Custom' { $CharArray = [char[]]@() $NumberOfChars = $Signs 1..$Length | ForEach-Object { $CurrentChar = $_ $Decider = Get-Random -Minimum 0 -Maximum 100 $CharsLeft = ($Length + 1 - $CurrentChar) $Chance = ($NumberOfChars / $CharsLeft * 100) if ($Decider -lt $Chance -or $CharsLeft -le $NumberOfChars) { $CharArray += ($Arrays.Signs | Get-Random -Count 1) $NumberOfChars-- } else { $CharArray += ($Arrays.LettersAndDigits | Get-Random -Count 1) } } $PasswordString = $CharArray -join '' } } if ($ReturnSecureStringObject) { ConvertTo-SecureString -String $PasswordString -AsPlainText -Force } else { $PasswordString } } } #EndRegion '.\Public\New-Password.ps1' 154 #Region '.\Public\Remove-GitHubArtifact.ps1' 0 function Remove-GitHubArtifact { <# .SYNOPSIS Cleanup artifacts from GitHub repo .DESCRIPTION This script will remove all artifacts for a single repos or all repos for a given user .PARAMETER RepoName Defines a specific repository to remove artifacts for .PARAMETER GitHubSecret Defines the GitHubSecret (API Key) to use .PARAMETER GitHubOrg Defines the GitHub owner user name .PARAMETER Repo Optionally specify a repo to only remove artifacts for that specific repo .PARAMETER PageSize Optionally specify the PageSize when retreiving repos and artifacts. Valid values are in range of 1..100. Default is 30. .LINK https://getps.dev/blog/cleanup-github-artifacts .EXAMPLE Remove-GitHubArtifact -GitHubSecret "ABC" -GitHubOrg "user" Running this function without specifying a repo will cleanup all artifacts for all repos .EXAMPLE Remove-GitHubArtifact -GitHubSecret "ABC" -GitHubOrg "user" -Repo "RepoName" Running the script with a specified repo will cleanup all artifacts for that repo #> [CmdletBinding(SupportsShouldProcess)] param( [parameter(Mandatory)] [string] $GitHubSecret, [parameter(Mandatory)] [string] $GitHubOrg, [parameter()] [string] $RepoName, [parameter()] [ValidateRange(1, 100)] [int] $PageSize = 30 ) $PSDefaultParameterValues = @{ 'Invoke-RestMethod:Headers' = @{Accept = 'application/vnd.github+json'; Authorization = "Bearer $GitHubSecret" } } # Find repos if ($RepoName) { $Repos = Invoke-RestMethod -Method get -Uri "https://api.github.com/repos/$GitHubOrg/$RepoName" } else { $Repos = [System.Collections.Generic.List[Object]]::New() $PageID = 1 do { $Result = Invoke-RestMethod -Method get -Uri "https://api.github.com/user/repos?per_page=$PageSize&page=$PageID" if ($Result) { $Repos.AddRange([array]$Result) } $PageID++ } until ($Result.Count -lt $PageSize) } foreach ($repo in $repos) { Write-Verbose -Message "Processing repo $($repo.name)" # Define result object $ObjectHash = [ordered]@{ Repo = $Repo.Name Artifacts_Found = 0 Artifacts_Removed = 0 Artifacts_SizeMB = 0 Artifacts = [System.Collections.Generic.List[Object]]::New() } # Find artifacts $Artifacts = [System.Collections.Generic.List[Object]]::New() $PageID = 1 do { $Result = Invoke-RestMethod -Method get -Uri "https://api.github.com/repos/$GitHubOrg/$($Repo.Name)/actions/artifacts?per_page=$PageSize&page=$PageID" | Select-Object -ExpandProperty artifacts if ($Result) { $Artifacts.AddRange([array]$Result) } $PageID++ } until ($Result.Count -lt $PageSize) # Remove artifacts if ($artifacts) { $ObjectHash.Artifacts_Found = $Artifacts.Count $ObjectHash.Artifacts_SizeMB = (($Artifacts | Measure-Object -Sum -Property size_in_bytes).Sum / 1MB) foreach ($artifact in $artifacts) { if ($PSCmdlet.ShouldProcess("Artifact: $($artifact.name) in Repo: $($Repo.Name)", 'DELETE')) { $Result = Invoke-RestMethod -Method DELETE -Uri "https://api.github.com/repos/$GitHubOrg/$($Repo.Name)/actions/artifacts/$($artifact.id)" $ObjectHash.Artifact_Removed++ } } } # Return resultobject [pscustomobject]$ObjectHash } } #EndRegion '.\Public\Remove-GitHubArtifact.ps1' 118 #Region '.\Public\Resolve-IPinSubnet.ps1' 0 function Resolve-IPinSubnet { <# .DESCRIPTION Checks if a specified IP address is included in the IP range of a specific network. .PARAMETER IP Defines the IP address to resolve. .PARAMETER Network Defines the network address to search within .PARAMETER MaskLength Defines the length of the mask .EXAMPLE Resolve-IPinSubnet -IP 213.199.154.5 -Network 213.199.154.0 -MaskLength 24 Checks if the IP 212.199.154.5 is included in the 213.199.154.0/24 network #> [CmdletBinding()] [OutputType([system.boolean])] param( [Parameter(Mandatory = $true)][string]$IP, [Parameter(Mandatory = $true)][string]$Network, [Parameter(Mandatory = $true)][int]$MaskLength ) $IPDec = [uint32](ConvertTo-DecimalIP -IPAddress $IP) $NetworkDec = [uint32](ConvertTo-DecimalIP -IPAddress $Network) $Mask = [uint32](ConvertTo-DecimalIP -IPAddress (ConvertTo-Mask -MaskLength $MaskLength)) if ($NetworkDec -eq ($Mask -band $IPDec)) { return $true } else { return $false } } #EndRegion '.\Public\Resolve-IPinSubnet.ps1' 37 #Region '.\Public\Set-EnvironmentVariable.ps1' 0 function Set-EnvironmentVariable { <# .DESCRIPTION Functions that provides a shortcut to create environmental variables .PARAMETER Name Defines the name of the envioronmental variable .PARAMETER Value Defines the value of the environmental variable .PARAMETER Target Defines the target for the environmental variable. Valid values are Machine, User, Process. Defaults to Process. This means that the configured environmental variables are non-persistant. If persistant environmental variables are desirable user Machine or User. .EXAMPLE Set-EnvironmentVariable -Name 'ComputerOwner' -Value 'Will Smith' -Target Machine This example creates the environment variable computerowner to the machine scope and assigns the value 'Will Smith' #> [CmdletBinding(SupportsShouldProcess)] param( [string] [Parameter(Mandatory)] $Name, [string] [AllowEmptyString()] [Parameter(Mandatory)] $Value, [System.EnvironmentVariableTarget] [ValidateSet('Machine', 'User', 'Process')] $Target = 'Process' ) # Force target to process on Linux and MacOS and warn user. if ($Target -ne 'Process' -and ($IsLinux -or $IsMacOS)) { Write-Warning -Message 'It is only supported to set process environment variables on Linux and MacOS, environment varable will be set in Process scope' $Target = [System.EnvironmentVariableTarget]::Process } if ($PSCmdlet.ShouldProcess($Name)) { [Environment]::SetEnvironmentVariable($Name, $Value, $Target) } } #EndRegion '.\Public\Set-EnvironmentVariable.ps1' 47 #Region '.\Public\Switch-Object.ps1' 0 function Switch-Object { <# .DESCRIPTION Transposes an object, foreach parameter an object is created .PARAMETER InputObject Defined the object to transpose .EXAMPLE Get-Process | Select-object -first 1 | Switch-object Description #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)]$InputObject ) PROCESS { $InputObject | ForEach-Object { $instance = $_ $instance.PSObject.Properties.Name | ForEach-Object { [PSCustomObject]@{ Name = $_ Value = $instance.$_ } } } } } #EndRegion '.\Public\Switch-Object.ps1' 27 #Region '.\Public\Test-AllHashKeysAreTrue.ps1' 0 function Test-AllHashKeysAreTrue { <# .DESCRIPTION This functions checks that all values of a hashtable evaluates to true. For values not of type boolean, a typecast to bool is performed. .PARAMETER HashTable Defines the hashtable object to test .EXAMPLE Validate-AllHashKeysAreTrue Description of example #> [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)][hashtable]$HashTable ) PROCESS { $AllTrue = $true foreach ($Key in $HashTable.Keys) { if ($HashTable.$Key -as [boolean] -eq $false) { $AllTrue = $false break } } Write-Output $AllTrue } } #EndRegion '.\Public\Test-AllHashKeysAreTrue.ps1' 28 #Region '.\Public\Test-Office365IPURL.ps1' 0 function Test-Office365IPURL { <# .DESCRIPTION Retreive a list of ip and urls required for communication to and from Office 365. .PARAMETER IP Defines the IP to search for with in the scopes of rules returned from Office 365. .PARAMETER Office365IPURL Defines the URL to the Office 365 IP URL Endpoint. Defaults to 'https://endpoints.office.com/endpoints/worldwide?clientrequestid=b10c5ed1-bad1-445f-b386-b919946339a7'. Provided as parameter to allow queries to other environments than worldwide as well as keep agility if Microsoft would change URL. .EXAMPLE Get-Office365IPURL -Services Exchange,Skype -OnlyRequired -Types IP4,URL -Outputformat JSON #> [CmdletBinding()] param( [Parameter(Mandatory)] [string[]] $IP, [Parameter()] [string] $Office365IPURL = 'https://endpoints.office.com/endpoints/worldwide?clientrequestid=b10c5ed1-bad1-445f-b386-b919946339a7' ) $ErrorActionPreference = 'Stop' # Get latest IP URL info $Office365Endpoints = Invoke-RestMethod -Uri $Office365IPURL -Method Get # Import net module Import-Module indented.net.ip # Foreach service foreach ($item in $IP) { # Foreach rule in service foreach ($rule in $Office365Endpoints) { # Select Ipv4 ips $IPv4Ranges = $rule.ips.where({ $_ -notlike '*:*' }) # Resolve IPs for URLs. There are two shortcomings of this part. First; Only the currently returned IPs are evaluated. In case other # records are returned due to GeoDNS, round robin etc those will not be known and therefor not evaluated. Second; URLs with wildcards are # not evalutated, there is no way for the script to know which URLs within the wildcard scope that will be called by services. $rule.urls | ForEach-Object { if ($_) { Resolve-DnsName $_ -ErrorAction SilentlyContinue | Where-Object { $_.GetType().Name -eq 'DnsRecord_A' } | ForEach-Object { $IPv4Ranges += $_.IPAddress } } } # Test each entry in the array if the IP is equal or belongs to the returned IP/range foreach ($range in $IPv4Ranges) { [pscustomobject]@{ RuleID = $rule.id ServiceArea = $rule.ServiceArea TCPPort = $rule.tcpPorts UDPPort = $rule.udpPorts Required = $rule.Required Range = $range Subject = $item IsMember = (Test-SubnetMember -SubjectIPAddress $item -ObjectIPAddress $range) } } } } } #EndRegion '.\Public\Test-Office365IPURL.ps1' 77 #Region '.\Public\Test-PasswordAgainstPwnedPasswordService.ps1' 0 function Test-PasswordAgainstPwnedPasswordService { <# .DESCRIPTION Return true if provided password is compromised .PARAMETER InputObject Defines the password to check .EXAMPLE Test-PasswordAgainstPwnedPasswordService Description of example #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingBrokenHashAlgorithms', '', Justification = 'API requires SHA1 hashes')] [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)] [System.Security.SecureString] $InputObject ) PROCESS { $hash = Get-StringHash -Strings (Convert-Object -FromSecureStringObject $InputObject -Property String) -Algorithm SHA1 -Iterations 0 $First5HashChars = $hash.hash.ToString().SubString(0, 5) $RemainingHashChars = $hash.hash.ToString().SubString(5) $url = "https://api.pwnedpasswords.com/range/$First5HashChars" [Net.ServicePointManager]::SecurityProtocol = 'Tls12' $response = Invoke-RestMethod -Uri $url -UseBasicParsing $lines = $response -split '\n' $filteredLines = $lines -like "$remainingHashChars*" return ([boolean]([int]($filteredLines -split ':')[-1])) } } #EndRegion '.\Public\Test-PasswordAgainstPwnedPasswordService.ps1' 35 #Region '.\Public\Test-PSGalleryNameAvailability.ps1' 0 function Test-PSGalleryNameAvailability { <# .DESCRIPTION Retreive a list of ip and urls required for communication to and from Office 365. .PARAMETER PackageName Defines the package name to search for .EXAMPLE Test-PSGalleryNameAvailability -PackageName PowershellGet #> [CmdletBinding()] [OutputType([boolean])] param( [Parameter(Mandatory)] [string] $PackageName ) $Response = Invoke-WebRequest -Uri "https://www.powershellgallery.com/packages/$PackageName" -SkipHttpErrorCheck if ($Response.RawContent -like '*Page not found*') { return $true } else { return $false } } #EndRegion '.\Public\Test-PSGalleryNameAvailability.ps1' 32 #Region '.\Public\Test-RebootPending.ps1' 0 function Test-RebootPending { <# .DESCRIPTION Queries the registry for the rebootpending key and returns the status .PARAMETER Name Description .EXAMPLE Test-RebootPending Description of example #> [CmdletBinding()] param( ) PROCESS { $rebootRequired = Test-Path -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending' return $rebootRequired } } #EndRegion '.\Public\Test-RebootPending.ps1' 23 #Region '.\suffix.ps1' 0 # The content of this file will be appended to the top of the psm1 module file. This is useful for custom procesedures after all module functions are loaded. #EndRegion '.\suffix.ps1' 2 |