diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d97e1a5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +tab_width = 4 diff --git a/Convert-EmlFile.ps1 b/Convert-EmlFile.ps1 index 29f2e9f..2bbb674 100644 --- a/Convert-EmlFile.ps1 +++ b/Convert-EmlFile.ps1 @@ -3,16 +3,16 @@ <# .SYNOPSIS Function will parse an eml files. - + .DESCRIPTION Function will parse eml file and return a normalized object that can be used to extract infromation from the encoded file. - + .PARAMETER EmlFileName A string representing the eml file to parse. - + .EXAMPLE PS C:\> Convert-EmlFile -EmlFileName 'C:\Test\test.eml' - + .OUTPUTS System.Object #> @@ -25,21 +25,21 @@ [string] $EmlFileName ) - + # Instantiate new ADODB Stream object $adoStream = New-Object -ComObject 'ADODB.Stream' - + # Open stream $adoStream.Open() - + # Load file $adoStream.LoadFromFile($EmlFileName) - + # Instantiate new CDO Message Object $cdoMessageObject = New-Object -ComObject 'CDO.Message' - + # Open object and pass stream $cdoMessageObject.DataSource.OpenObject($adoStream, '_Stream') - + return $cdoMessageObject } \ No newline at end of file diff --git a/Export-Registry.ps1 b/Export-Registry.ps1 index d99af54..c9952f1 100644 --- a/Export-Registry.ps1 +++ b/Export-Registry.ps1 @@ -3,38 +3,38 @@ function Export-Registry <# .SYNOPSIS Export registry item properties. - + .DESCRIPTION Export item properties for a given registry key. - + By default results will be written to the pipeline unless the -ExportFormat parameter is used. - + .PARAMETER KeyPath A string representing the Key(s) to export in the PsDrive format IE: HKCU:\SOFTWARE\TestSoftware - + .PARAMETER ExportFormat A string representing the format to use for the export. - - Possible values are: - + + Possible values are: + - CSV - XML - + PArameter is used in conjunction with the ExportPath paramter. - + .PARAMETER ExportPath A string representing the path where keys should be exported. - + .PARAMETER NoBinaryData When parameter is specified any binary data present in the registry key is removed. - + .EXAMPLE PS C:\> Export-RegistryNew -KeyPath 'HKCU:\SOFTWARE\TestSoftware' - + .NOTES Additional information about the function. #> - + [CmdletBinding(DefaultParameterSetName = 'PrintOnly')] param ( @@ -65,13 +65,13 @@ function Export-Registry [switch] $NoBinaryData ) - + begin { - # Initialize results array + # Initialize results array [System.Collections.ArrayList]$returnData = @() } - + process { # Go through all paths @@ -80,25 +80,25 @@ function Export-Registry if ((Test-IsRegistryKey -KeyPath $path) -eq $true) { Write-Verbose "Getting properties for key: $path" - + # Get registry item $paramGetItem = @{ Path = $path ErrorAction = 'Stop' } - + [Microsoft.Win32.RegistryKey]$regItem = Get-Item @paramGetItem - + # Get key properties [array]$regItemProperties = $regItem.'Property' - + if ($regItemProperties.Count -gt 0) { # Enumerate properties foreach ($property in $regItemProperties) { Write-Verbose "Exporting $property" - + # Append data to return array [void]($returnData.Add([pscustomobject]@{ 'Path' = $regItem @@ -124,12 +124,12 @@ function Export-Registry else { Write-Warning -Message "Key $path does not exist" - + continue } } } - + end { # Check we have results @@ -143,11 +143,11 @@ function Export-Registry if ($PSBoundParameters.ContainsKey('NoBinaryData')) { Write-Verbose -Message 'Removing binary data from return values' - + # Remove binary data $returnData = $returnData | Where-Object { $_.Type -ne 'Binary' } } - + switch ($ExportFormat) { 'csv' @@ -161,14 +161,14 @@ function Export-Registry $returnData | Export-Clixml -Path $ExportPath -Force } } - + Write-Verbose -Message "Data written to $ExportPath" } - + default { Write-Verbose -Message 'No data will be exported' - + # Print on screen only $returnData } diff --git a/Get-ElapsedTime.ps1 b/Get-ElapsedTime.ps1 index 821c3a2..148d596 100644 --- a/Get-ElapsedTime.ps1 +++ b/Get-ElapsedTime.ps1 @@ -1,183 +1,183 @@ function Get-ElapsedTime { - <# - .SYNOPSIS - Will return information about elapsed time for the given StopWatch. - - .DESCRIPTION - Function requires a [System.Diagnostics.Stopwatch] object as input and will output information about elapsed time. - - By default a [TimeSpan] object is returned containing all information about elapsed time. - - If any other parameter like -Days is used function will return an Int or Double instead depending on the switch used. - - .PARAMETER ElapsedTime - Will return a [TimeSpan] object representing the elapsed time for the given stopwatch. - - .PARAMETER Days - Will return an [Int] object representing the number of days since the stopwatch was started. - - .PARAMETER Hours - Will return an [Int] object representing the number of hours since the stopwatch was started. - - .PARAMETER Minutes - Will return an [Int] object representing the number of minutes since the stopwatch was started. - - .PARAMETER Seconds - Will return an [Int] object representing the number of seconds since the stopwatch was started. - - .PARAMETER TotalDays - Will return a [Double] object representing the number of TotalDays since the stopwatch was started. - - .PARAMETER TotalHours - Will return a [Double] object representing the number of TotalHours since the stopwatch was started. - - .PARAMETER TotalMinutes - Will return a [Double] object representing the number of TotalMinutes since the stopwatch was started. - - .PARAMETER TotalSeconds - Will return a [Double] object representing the number of TotalSeconds since the stopwatch was started. - - .PARAMETER TotalMilliseconds - Will return a [Double] object representing the number of TotalMilliseconds since the stopwatch was started. - - .EXAMPLE - PS C:\> Get-ElapsedTime -ElapsedTime $ElapsedTime -Days - - .OUTPUTS - System.TimeSpan, System.Double, System.Int32 - #> - - [CmdletBinding(DefaultParameterSetName = 'FullOutput', - ConfirmImpact = 'High', - SupportsPaging = $false, - SupportsShouldProcess = $false)] - [OutputType([timespan], ParameterSetName = 'FullOutput')] - [OutputType([int], ParameterSetName = 'Days')] - [OutputType([int], ParameterSetName = 'Hours')] - [OutputType([int], ParameterSetName = 'Minutes')] - [OutputType([int], ParameterSetName = 'Seconds')] - [OutputType([double], ParameterSetName = 'TotalDays')] - [OutputType([double], ParameterSetName = 'TotalHours')] - [OutputType([double], ParameterSetName = 'TotalMinutes')] - [OutputType([double], ParameterSetName = 'TotalSeconds')] - [OutputType([double], ParameterSetName = 'TotalMilliseconds')] - [OutputType([timespan])] - param - ( - [Parameter(ParameterSetName = 'FullOutput', - Mandatory = $true)] - [Parameter(ParameterSetName = 'Days')] - [Parameter(ParameterSetName = 'Hours')] - [Parameter(ParameterSetName = 'Minutes')] - [Parameter(ParameterSetName = 'Seconds')] - [Parameter(ParameterSetName = 'TotalDays')] - [Parameter(ParameterSetName = 'TotalHours')] - [Parameter(ParameterSetName = 'TotalMilliseconds')] - [Parameter(ParameterSetName = 'TotalMinutes')] - [Parameter(ParameterSetName = 'TotalSeconds')] - [System.Diagnostics.Stopwatch] - $ElapsedTime, - [Parameter(ParameterSetName = 'Days')] - [switch] - $Days, - [Parameter(ParameterSetName = 'Hours')] - [switch] - $Hours, - [Parameter(ParameterSetName = 'Minutes')] - [switch] - $Minutes, - [Parameter(ParameterSetName = 'Seconds')] - [switch] - $Seconds, - [Parameter(ParameterSetName = 'TotalDays')] - [switch] - $TotalDays, - [Parameter(ParameterSetName = 'TotalHours')] - [switch] - $TotalHours, - [Parameter(ParameterSetName = 'TotalMinutes')] - [switch] - $TotalMinutes, - [Parameter(ParameterSetName = 'TotalSeconds')] - [switch] - $TotalSeconds, - [Parameter(ParameterSetName = 'TotalMilliseconds')] - [switch] - $TotalMilliseconds - ) - - switch ($PsCmdlet.ParameterSetName) - { - 'FullOutput' - { - # Return full timespan object - return $ElapsedTime.Elapsed - - break - } - 'Days' - { - # Return days with no decimals - return $ElapsedTime.Elapsed.Days - - break - } - 'Hours' - { - # Return hours with no decimals - return $ElapsedTime.Elapsed.Hours - - break - } - 'Minutes' - { - # Return minutes with no decimals - return $ElapsedTime.Elapsed.Minutes - - break - } - 'Seconds' - { - # Return seconds with no decimals - return $ElapsedTime.Elapsed.Seconds - - break - } - 'TotalDays' - { - # Return days with double precision - return $ElapsedTime.Elapsed.TotalDays - - break - } - 'TotalHours' - { - # Return hours with double precision - return $ElapsedTime.Elapsed.TotalHours - - break - } - 'TotalMinutes' - { - # Return minutes with double precision - return $ElapsedTime.Elapsed.TotalMinutes - - break - } - 'TotalSeconds' - { - # Return seconds with double precision - return $ElapsedTime.Elapsed.TotalSeconds - - break - } - 'TotalMilliseconds' - { - # Return milliseconds with double precision - return $ElapsedTime.Elapsed.TotalMilliseconds - - break - } - } + <# + .SYNOPSIS + Will return information about elapsed time for the given StopWatch. + + .DESCRIPTION + Function requires a [System.Diagnostics.Stopwatch] object as input and will output information about elapsed time. + + By default a [TimeSpan] object is returned containing all information about elapsed time. + + If any other parameter like -Days is used function will return an Int or Double instead depending on the switch used. + + .PARAMETER ElapsedTime + Will return a [TimeSpan] object representing the elapsed time for the given stopwatch. + + .PARAMETER Days + Will return an [Int] object representing the number of days since the stopwatch was started. + + .PARAMETER Hours + Will return an [Int] object representing the number of hours since the stopwatch was started. + + .PARAMETER Minutes + Will return an [Int] object representing the number of minutes since the stopwatch was started. + + .PARAMETER Seconds + Will return an [Int] object representing the number of seconds since the stopwatch was started. + + .PARAMETER TotalDays + Will return a [Double] object representing the number of TotalDays since the stopwatch was started. + + .PARAMETER TotalHours + Will return a [Double] object representing the number of TotalHours since the stopwatch was started. + + .PARAMETER TotalMinutes + Will return a [Double] object representing the number of TotalMinutes since the stopwatch was started. + + .PARAMETER TotalSeconds + Will return a [Double] object representing the number of TotalSeconds since the stopwatch was started. + + .PARAMETER TotalMilliseconds + Will return a [Double] object representing the number of TotalMilliseconds since the stopwatch was started. + + .EXAMPLE + PS C:\> Get-ElapsedTime -ElapsedTime $ElapsedTime -Days + + .OUTPUTS + System.TimeSpan, System.Double, System.Int32 + #> + + [CmdletBinding(DefaultParameterSetName = 'FullOutput', + ConfirmImpact = 'High', + SupportsPaging = $false, + SupportsShouldProcess = $false)] + [OutputType([timespan], ParameterSetName = 'FullOutput')] + [OutputType([int], ParameterSetName = 'Days')] + [OutputType([int], ParameterSetName = 'Hours')] + [OutputType([int], ParameterSetName = 'Minutes')] + [OutputType([int], ParameterSetName = 'Seconds')] + [OutputType([double], ParameterSetName = 'TotalDays')] + [OutputType([double], ParameterSetName = 'TotalHours')] + [OutputType([double], ParameterSetName = 'TotalMinutes')] + [OutputType([double], ParameterSetName = 'TotalSeconds')] + [OutputType([double], ParameterSetName = 'TotalMilliseconds')] + [OutputType([timespan])] + param + ( + [Parameter(ParameterSetName = 'FullOutput', + Mandatory = $true)] + [Parameter(ParameterSetName = 'Days')] + [Parameter(ParameterSetName = 'Hours')] + [Parameter(ParameterSetName = 'Minutes')] + [Parameter(ParameterSetName = 'Seconds')] + [Parameter(ParameterSetName = 'TotalDays')] + [Parameter(ParameterSetName = 'TotalHours')] + [Parameter(ParameterSetName = 'TotalMilliseconds')] + [Parameter(ParameterSetName = 'TotalMinutes')] + [Parameter(ParameterSetName = 'TotalSeconds')] + [System.Diagnostics.Stopwatch] + $ElapsedTime, + [Parameter(ParameterSetName = 'Days')] + [switch] + $Days, + [Parameter(ParameterSetName = 'Hours')] + [switch] + $Hours, + [Parameter(ParameterSetName = 'Minutes')] + [switch] + $Minutes, + [Parameter(ParameterSetName = 'Seconds')] + [switch] + $Seconds, + [Parameter(ParameterSetName = 'TotalDays')] + [switch] + $TotalDays, + [Parameter(ParameterSetName = 'TotalHours')] + [switch] + $TotalHours, + [Parameter(ParameterSetName = 'TotalMinutes')] + [switch] + $TotalMinutes, + [Parameter(ParameterSetName = 'TotalSeconds')] + [switch] + $TotalSeconds, + [Parameter(ParameterSetName = 'TotalMilliseconds')] + [switch] + $TotalMilliseconds + ) + + switch ($PsCmdlet.ParameterSetName) + { + 'FullOutput' + { + # Return full timespan object + return $ElapsedTime.Elapsed + + break + } + 'Days' + { + # Return days with no decimals + return $ElapsedTime.Elapsed.Days + + break + } + 'Hours' + { + # Return hours with no decimals + return $ElapsedTime.Elapsed.Hours + + break + } + 'Minutes' + { + # Return minutes with no decimals + return $ElapsedTime.Elapsed.Minutes + + break + } + 'Seconds' + { + # Return seconds with no decimals + return $ElapsedTime.Elapsed.Seconds + + break + } + 'TotalDays' + { + # Return days with double precision + return $ElapsedTime.Elapsed.TotalDays + + break + } + 'TotalHours' + { + # Return hours with double precision + return $ElapsedTime.Elapsed.TotalHours + + break + } + 'TotalMinutes' + { + # Return minutes with double precision + return $ElapsedTime.Elapsed.TotalMinutes + + break + } + 'TotalSeconds' + { + # Return seconds with double precision + return $ElapsedTime.Elapsed.TotalSeconds + + break + } + 'TotalMilliseconds' + { + # Return milliseconds with double precision + return $ElapsedTime.Elapsed.TotalMilliseconds + + break + } + } } diff --git a/Get-TimerStatus.ps1 b/Get-TimerStatus.ps1 index ede44b3..cf7998e 100644 --- a/Get-TimerStatus.ps1 +++ b/Get-TimerStatus.ps1 @@ -1,29 +1,29 @@ function Get-TimerStatus { - <# - .SYNOPSIS - Will return boolean value representing status of an existing stopwatch. - - .DESCRIPTION - Function requires a [System.Diagnostics.Stopwatch] object as input and will return $True if stopwatch is running or $False otherwise. - - .PARAMETER Timer - A [System.Diagnostics.Stopwatch] object representing the StopWatch to check status for. - - .EXAMPLE - PS C:\> Get-TimerStatus -Timer $Timer - - .OUTPUTS - System.Boolean - #> - - [OutputType([bool])] - param - ( - [Parameter(Mandatory = $true)] - [System.Diagnostics.Stopwatch] - $Timer - ) - - return $Timer.IsRunning + <# + .SYNOPSIS + Will return boolean value representing status of an existing stopwatch. + + .DESCRIPTION + Function requires a [System.Diagnostics.Stopwatch] object as input and will return $True if stopwatch is running or $False otherwise. + + .PARAMETER Timer + A [System.Diagnostics.Stopwatch] object representing the StopWatch to check status for. + + .EXAMPLE + PS C:\> Get-TimerStatus -Timer $Timer + + .OUTPUTS + System.Boolean + #> + + [OutputType([bool])] + param + ( + [Parameter(Mandatory = $true)] + [System.Diagnostics.Stopwatch] + $Timer + ) + + return $Timer.IsRunning } \ No newline at end of file diff --git a/Get-UniqueUPN.ps1 b/Get-UniqueUPN.ps1 index a7562e1..58a9ece 100644 --- a/Get-UniqueUPN.ps1 +++ b/Get-UniqueUPN.ps1 @@ -3,50 +3,50 @@ function Get-UniqueUPN <# .SYNOPSIS Cmdlet will generate a forest wide unique UPN. - + .DESCRIPTION Cmdlet will generate a forest wide unique UPN according to generation rules defined by the user. - + Cmdlet accept different types of objects to generate the UPN to allow greater flexibility - + ADObject - For example and object from Get-AdUser cmdlet Strings - Representing First Name, Last Name etc. DirectoryService Objects - For example when using native .Net methods to retrieve the identity - + .PARAMETER ADObject An ADObject for example output of the Get-ADUser cmdlet - + .PARAMETER FirstName A string representing the First Name of the user - + .PARAMETER LastName A string representing the Last Name of the user - + .PARAMETER MiddleName A string representing the Middle Name of the user, parameter is optional. - + .PARAMETER UPNSuffix A string representing the UPN suffix to be used. - + .PARAMETER FirstNameFormat A string representing the format to be for the First Name part of the UPN. - + .PARAMETER LastNameFormat A string representing the format to be for the Last Name part of the UPN. - + .PARAMETER IncludeMiddleName When paramenter is specified user Middle Name, if present, will be included in the UPN generation process. - + .PARAMETER ADServer A string representing the name of the AD Domain Controller that will be used to query Active Directory. - + If no server is specified the closest Global Catalog will be automatically selected. - + .PARAMETER Separator A string representing the separator to be used between UPN parts, defaults to a '.'. #> - + [CmdletBinding(DefaultParameterSetName = 'Strings')] param ( @@ -79,7 +79,7 @@ function Get-UniqueUPN [ValidateNotNullOrEmpty()] [string]$Separator = '.' ) - + if ($PSCmdlet.ParameterSetName -eq 'ADObject') { switch ($ADObject.GetType().FullName) @@ -89,7 +89,7 @@ function Get-UniqueUPN [string]$firstName = $ADObject.GivenName [string]$lastName = $ADObject.Surname [string]$middleName = $ADObject.MiddleName - + break } 'System.DirectoryServices.DirectoryEntry' @@ -97,7 +97,7 @@ function Get-UniqueUPN [string]$firstName = $ADObject.Properties['givenName'][0] [string]$lastName = $ADObject.Properties['sn'][0] [string]$middleName = $ADObject.Properties['middleName'][0] - + break } 'System.DirectoryServices.SearchResult' @@ -105,7 +105,7 @@ function Get-UniqueUPN [string]$firstName = $ADObject.Properties['givenName'][0] [string]$lastName = $ADObject.Properties['sn'][0] [string]$middleName = $ADObject.Properties['middleName'][0] - + break } default @@ -120,7 +120,7 @@ function Get-UniqueUPN [string]$lastName = $LastName [string]$middleName = $MiddleName } - + # Format first name $firstName = switch ($FirstNameFormat) { @@ -133,7 +133,7 @@ function Get-UniqueUPN $firstName.Substring(0, 1) } } - + # Format last name $LastName = switch ($FirstNameFormat) { @@ -146,24 +146,24 @@ function Get-UniqueUPN $LastName.Substring(0, 1) } } - + # Use middle name [string]$middleNamePart = if ($IncludeMiddleName -and $MiddleName) { '{0}{1}' -f $Separator, $MiddleName } - + # Setup required attributes [string]$baseUPN = ('{0}{1}{2}{3}@{4}' -f $FirstName, $middleNamePart, $Separator, $LastName, $UPNSuffix).ToLower() [string]$uniqueUPN = $baseUPN [int]$counter = 1 - + while (Test-UPNExist -UPN $uniqueUPN -Server $ADServer) { $uniqueUPN = '{0}{1}@{2}' -f ($baseUPN.Split('@')[0]), $counter, $UPNSuffix - + $counter++ } - + return $uniqueUPN } \ No newline at end of file diff --git a/New-ApiRequest.ps1 b/New-ApiRequest.ps1 index 09d0651..e3a907b 100644 --- a/New-ApiRequest.ps1 +++ b/New-ApiRequest.ps1 @@ -1,121 +1,121 @@ function New-ApiRequest { <# - .SYNOPSIS - Function will query data from an URL API. - - .DESCRIPTION - Function is intended as a wrapper around PowerShell built-in Invoke-RestMethod cmdlet allowing user to quickly generate web requests to APIs requiring OAuth2 authentication. - - .PARAMETER ApiKey - A string representing the API key to be used. - - .PARAMETER ApiSecret - A string representing the API key secret. - - .PARAMETER ApiUrl - A string representing the API endpoint URL - - .PARAMETER GrantType - A string representing the Grant Type supported by the API. - - If not specified it will default to client_credentials. - - .PARAMETER ContentType - A string representing the content type to send as part of the request in case request needs to be crafted with a special ContentType. - - .PARAMETER Method - A string representing the method to be used in the web request. - - If not specified it will default to GET. - - .PARAMETER Headers - A string representing custom headers to send as part of the webrequest. - - .EXAMPLE - PS C:\> New-ApiRequest -ApiKey 'Value1' -ApiUrl 'Value2' -ApiSecret 'MySecret' - - .NOTES - Additional information about the function. + .SYNOPSIS + Function will query data from an URL API. + + .DESCRIPTION + Function is intended as a wrapper around PowerShell built-in Invoke-RestMethod cmdlet allowing user to quickly generate web requests to APIs requiring OAuth2 authentication. + + .PARAMETER ApiKey + A string representing the API key to be used. + + .PARAMETER ApiSecret + A string representing the API key secret. + + .PARAMETER ApiUrl + A string representing the API endpoint URL + + .PARAMETER GrantType + A string representing the Grant Type supported by the API. + + If not specified it will default to client_credentials. + + .PARAMETER ContentType + A string representing the content type to send as part of the request in case request needs to be crafted with a special ContentType. + + .PARAMETER Method + A string representing the method to be used in the web request. + + If not specified it will default to GET. + + .PARAMETER Headers + A string representing custom headers to send as part of the webrequest. + + .EXAMPLE + PS C:\> New-ApiRequest -ApiKey 'Value1' -ApiUrl 'Value2' -ApiSecret 'MySecret' + + .NOTES + Additional information about the function. #> - - [CmdletBinding()] - [OutputType([pscustomobject])] - param - ( - [Parameter(Mandatory = $true)] - [string] - $ApiKey, - [string] - $ApiSecret, - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string] - $ApiUrl, - [ValidateNotNullOrEmpty()] - [string] - $GrantType = 'client_credentials', - [ValidateNotNullOrEmpty()] - [string] - $ContentType, - [ValidateNotNullOrEmpty()] - [ValidateSet('GET', 'POST', IgnoreCase = $true)] - [string] - $Method = 'GET', - [ValidateNotNullOrEmpty()] - [string] - $Headers - ) - - Process - { - # Generate request post data - [hashtable]$requestBody = @{ - 'client_id' = $ApiKey; - 'grant_type' = $GrantType - } - - switch ($PSBoundParameters.Keys) - { - 'ContentType' - { - $requestBody.Add('ContentType', $ContentType) - - break - } - 'ApiSecret' - { - $requestBody.Add('client_secret', $ApiSecret) - - break - } - } - - # Define splat command - $paramInvokeWebRequest = @{ - Uri = $ApiUrl - Body = $requestBody - } - - # Get passed parameters - switch ($PSBoundParameters.Keys) - { - 'Headers' - { - # Add custom header - $paramInvokeWebRequest.Add('Headers', $Headers) - - break - } - 'Method' - { - # Use custom method - $paramInvokeWebRequest.Add('Method', $Method) - - break - } - } - - return Invoke-RestMethod @paramInvokeWebRequest - } + + [CmdletBinding()] + [OutputType([pscustomobject])] + param + ( + [Parameter(Mandatory = $true)] + [string] + $ApiKey, + [string] + $ApiSecret, + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $ApiUrl, + [ValidateNotNullOrEmpty()] + [string] + $GrantType = 'client_credentials', + [ValidateNotNullOrEmpty()] + [string] + $ContentType, + [ValidateNotNullOrEmpty()] + [ValidateSet('GET', 'POST', IgnoreCase = $true)] + [string] + $Method = 'GET', + [ValidateNotNullOrEmpty()] + [string] + $Headers + ) + + Process + { + # Generate request post data + [hashtable]$requestBody = @{ + 'client_id' = $ApiKey; + 'grant_type' = $GrantType + } + + switch ($PSBoundParameters.Keys) + { + 'ContentType' + { + $requestBody.Add('ContentType', $ContentType) + + break + } + 'ApiSecret' + { + $requestBody.Add('client_secret', $ApiSecret) + + break + } + } + + # Define splat command + $paramInvokeWebRequest = @{ + Uri = $ApiUrl + Body = $requestBody + } + + # Get passed parameters + switch ($PSBoundParameters.Keys) + { + 'Headers' + { + # Add custom header + $paramInvokeWebRequest.Add('Headers', $Headers) + + break + } + 'Method' + { + # Use custom method + $paramInvokeWebRequest.Add('Method', $Method) + + break + } + } + + return Invoke-RestMethod @paramInvokeWebRequest + } } \ No newline at end of file diff --git a/New-LogEntry/CHANGELOG.md b/New-LogEntry/CHANGELOG.md index fa6fc69..e9e5583 100644 --- a/New-LogEntry/CHANGELOG.md +++ b/New-LogEntry/CHANGELOG.md @@ -1,5 +1,14 @@ # New-LogEntry - Change History +## Maintenance parity with IT-ToolBox (unreleased) + +- Synchronize public logging implementation and private helpers with IT-ToolBox after PR #9. +- Treat redaction replacements literally, preventing matched secrets from being reinserted. +- Serialize buffer flushes, retain entries after failures and preserve appended entries. +- Resolve default paths from the caller, falling back to the interactive working directory. +- Reject conflicting buffered severity switches. +- Add regression and concurrent-process tests alongside the dot-source loader tests. + ## Version 2.1.0 - Refactored message formatting, console output, file writes, and buffer handling to remove duplicated logic diff --git a/New-LogEntry/New-LogEntry.ps1 b/New-LogEntry/New-LogEntry.ps1 index f7240af..250e145 100644 --- a/New-LogEntry/New-LogEntry.ps1 +++ b/New-LogEntry/New-LogEntry.ps1 @@ -18,6 +18,7 @@ $script:NewLogEntrySourceFiles = @( 'Private/Add-NewLogEntryBuffer.ps1' 'Private/Get-NewLogEntryBuffer.ps1' 'Private/Clear-NewLogEntryBuffer.ps1' + 'Private/Flush-NewLogEntryBuffer.ps1' 'Public/New-LogEntry.ps1' ) diff --git a/New-LogEntry/Private/ConvertTo-NewLogEntryRedactedMessage.ps1 b/New-LogEntry/Private/ConvertTo-NewLogEntryRedactedMessage.ps1 index e0426f1..9838c4a 100644 --- a/New-LogEntry/Private/ConvertTo-NewLogEntryRedactedMessage.ps1 +++ b/New-LogEntry/Private/ConvertTo-NewLogEntryRedactedMessage.ps1 @@ -18,7 +18,8 @@ function ConvertTo-NewLogEntryRedactedMessage continue } - $redactedMessage = [regex]::Replace($redactedMessage, $item, $Replacement) + # Escape dollar signs so replacement syntax cannot reinsert matched secrets. + $redactedMessage = [regex]::Replace($redactedMessage, $item, $Replacement.Replace('$', '$$')) } return $redactedMessage diff --git a/New-LogEntry/Private/Flush-NewLogEntryBuffer.ps1 b/New-LogEntry/Private/Flush-NewLogEntryBuffer.ps1 new file mode 100644 index 0000000..705d582 --- /dev/null +++ b/New-LogEntry/Private/Flush-NewLogEntryBuffer.ps1 @@ -0,0 +1,38 @@ +function Flush-NewLogEntryBuffer +{ + param( + [string]$Path, + [ValidateRange(1, 86400)] + [int]$LockTimeoutSeconds = 30 + ) + + Initialize-NewLogEntryState + + # Serialize snapshot, write, and removal with other operations on this buffer. + # A failed write leaves entries available for a later retry. + [System.Threading.Monitor]::Enter($script:NewLogEntryBufferLock) + try + { + $lines = $script:NewLogEntryBuffer.ToArray() + if ($lines.Count -eq 0) + { + return + } + + Write-NewLogEntryLines -Lines $lines -Path $Path -LockTimeoutSeconds $LockTimeoutSeconds + + # Remove only the written prefix, preserving any reentrant append during writing. + $script:NewLogEntryBuffer.RemoveRange(0, $lines.Count) + $script:messageBuffer = $script:NewLogEntryBuffer -join [Environment]::NewLine + if ($script:messageBuffer.Length -gt 0) + { + $script:messageBuffer += [Environment]::NewLine + } + } + finally + { + [System.Threading.Monitor]::Exit($script:NewLogEntryBufferLock) + } + + return $lines +} diff --git a/New-LogEntry/Private/Resolve-NewLogEntryPath.ps1 b/New-LogEntry/Private/Resolve-NewLogEntryPath.ps1 index cad8e7e..df27c19 100644 --- a/New-LogEntry/Private/Resolve-NewLogEntryPath.ps1 +++ b/New-LogEntry/Private/Resolve-NewLogEntryPath.ps1 @@ -1,19 +1,15 @@ function Resolve-NewLogEntryPath { - param([string]$Path) + param([string]$Path, [string]$CallerScriptPath) if (-not [string]::IsNullOrWhiteSpace($Path)) { return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) } - $basePath = if (-not [string]::IsNullOrWhiteSpace($script:PSCommandPath)) + $basePath = if (-not [string]::IsNullOrWhiteSpace($CallerScriptPath)) { - $script:PSCommandPath - } - elseif (-not [string]::IsNullOrWhiteSpace($PSCommandPath)) - { - $PSCommandPath + $CallerScriptPath } else { diff --git a/New-LogEntry/Public/New-LogEntry.ps1 b/New-LogEntry/Public/New-LogEntry.ps1 index af2cf27..916621b 100644 --- a/New-LogEntry/Public/New-LogEntry.ps1 +++ b/New-LogEntry/Public/New-LogEntry.ps1 @@ -49,7 +49,9 @@ function New-LogEntry Returns buffered log entries without clearing them. .PARAMETER FlushBuffer - Writes buffered entries to the log file and clears the buffer after a successful write. + Writes buffered entries to the log file and removes the written entries after a successful write. + A failed file write retains the buffer for retry. A partial filesystem write can leave + content on disk, so a retry after an I/O failure can duplicate that content. .PARAMETER ClearBuffer Clears buffered entries without writing them. @@ -72,7 +74,7 @@ function New-LogEntry Additional regular expression patterns to redact before formatting or writing the message. .PARAMETER RedactionText - Replacement text used for redacted content. Defaults to [REDACTED]. + Literal replacement text used for redacted content (regex substitutions are not expanded). Defaults to [REDACTED]. .PARAMETER NoTag Omits the severity tag from the formatted entry. @@ -170,6 +172,7 @@ function New-LogEntry begin { + $callerScriptPath = $MyInvocation.ScriptName $pendingEntries = [System.Collections.Generic.List[string]]::new() $activeRedactPatterns = [System.Collections.Generic.List[string]]::new() @@ -178,6 +181,12 @@ function New-LogEntry throw 'Use either -IsWarningMessage or -IsErrorMessage, not both.' } + $bufferSeverityCount = [int]$BufferOnlyInfo.IsPresent + [int]$BufferOnlyWarning.IsPresent + [int]$BufferOnlyError.IsPresent + if ($bufferSeverityCount -gt 1) + { + throw 'Use only one of -BufferOnlyInfo, -BufferOnlyWarning, or -BufferOnlyError.' + } + if ($PSBoundParameters.ContainsKey('Level') -and ($IsWarningMessage -or $IsErrorMessage -or $BufferOnlyWarning -or $BufferOnlyError -or $BufferOnlyInfo)) { throw 'Use either -Level or a compatibility severity switch, not both.' @@ -234,15 +243,14 @@ function New-LogEntry 'FlushBuffer' { - $bufferedLines = Get-NewLogEntryBuffer + $resolvedLogPath = Resolve-NewLogEntryPath -Path $LogFilePath -CallerScriptPath $callerScriptPath + $bufferedLines = @(Flush-NewLogEntryBuffer -Path $resolvedLogPath -LockTimeoutSeconds $LockTimeoutSeconds) if ($bufferedLines.Count -eq 0) { return } - Write-NewLogEntryLines -Lines $bufferedLines -Path $LogFilePath -LockTimeoutSeconds $LockTimeoutSeconds - if (-not $NoConsole) { foreach ($line in $bufferedLines) @@ -251,8 +259,6 @@ function New-LogEntry } } - Clear-NewLogEntryBuffer - if ($PassThru) { $bufferedLines @@ -294,7 +300,8 @@ function New-LogEntry return } - Write-NewLogEntryLines -Lines $entries -Path $LogFilePath -LockTimeoutSeconds $LockTimeoutSeconds + $resolvedLogPath = Resolve-NewLogEntryPath -Path $LogFilePath -CallerScriptPath $callerScriptPath + Write-NewLogEntryLines -Lines $entries -Path $resolvedLogPath -LockTimeoutSeconds $LockTimeoutSeconds if (-not $NoConsole) { diff --git a/New-LogEntry/README.md b/New-LogEntry/README.md index fb7a061..f496cf7 100644 --- a/New-LogEntry/README.md +++ b/New-LogEntry/README.md @@ -22,6 +22,7 @@ New-LogEntry/ │ ├── Add-NewLogEntryBuffer.ps1 │ ├── Clear-NewLogEntryBuffer.ps1 │ ├── ConvertTo-NewLogEntryRedactedMessage.ps1 +│ ├── Flush-NewLogEntryBuffer.ps1 │ ├── Format-NewLogEntry.ps1 │ ├── Get-NewLogEntryBuffer.ps1 │ ├── Get-NewLogEntryMutexName.ps1 @@ -125,7 +126,13 @@ Clear the buffer without writing it: New-LogEntry -ClearBuffer ``` -Buffer access is synchronized for runspace scenarios. Callers should interact with the buffer through the public parameters rather than accessing the backing state directly. +Buffer snapshot, file write and removal are synchronized as one operation. Failed +writes retain entries for retry; only the written prefix is removed on success. +A partial filesystem write can leave content on disk, so retrying after an I/O +failure does not guarantee exactly-once delivery. + +Without `-LogFilePath`, logs are created beside the calling script, or in the +current directory for interactive calls. Specify a path for a stable filename. Callers should interact with the buffer through the public parameters rather than accessing the backing state directly. ## Concurrent file writes @@ -174,6 +181,8 @@ New-LogEntry ` -RedactionText '' ``` +`-RedactionText` is literal text: dollar signs are not expanded as regex substitutions. + Redaction reduces accidental credential exposure in logs, but it should not be treated as a substitute for proper secret-management practices. ## Backward-compatible severity switches @@ -185,6 +194,8 @@ New-LogEntry -LogMessage 'Warning' -IsWarningMessage -LogFilePath './application New-LogEntry -LogMessage 'Failure' -IsErrorMessage -LogFilePath './application.log' ``` +Combining multiple buffered severity switches is rejected. Individual switches remain supported. + Mixing `-Level` with a legacy severity switch is rejected rather than silently choosing one behavior. ## Tests @@ -198,7 +209,10 @@ The project includes a Pester 5 test suite covering the current behavioral contr - pipeline batching - buffering, flushing, and clearing - pass-through output -- built-in and custom secret redaction +- built-in and custom secret redaction, including literal replacement text +- default paths from calling scripts and interactive sessions +- buffer retention and retry after failed writes +- simultaneous direct and buffered writes from three processes Install Pester if necessary: diff --git a/New-LogEntry/Tests/LoggerParity.Tests.ps1 b/New-LogEntry/Tests/LoggerParity.Tests.ps1 new file mode 100644 index 0000000..9a14978 --- /dev/null +++ b/New-LogEntry/Tests/LoggerParity.Tests.ps1 @@ -0,0 +1,195 @@ +BeforeAll { + $loaderPath = (Resolve-Path (Join-Path $PSScriptRoot '../New-LogEntry.ps1')).Path + New-Module -Name StandaloneNewLogEntry -ArgumentList $loaderPath -ScriptBlock { + param($Loader) + . $Loader + } | Import-Module -Force +} +AfterAll { Remove-Module StandaloneNewLogEntry } + +Describe 'New-LogEntry audit regressions' { + BeforeEach { + New-LogEntry -ClearBuffer + } + + It 'treats regex replacement expressions as literal text: ' -ForEach @( + @{ Replacement = '$0' } + @{ Replacement = '$1' } + @{ Replacement = '$&' } + @{ Replacement = '$$' } + @{ Replacement = '${secret}' } + ) { + $path = Join-Path $TestDrive ([guid]::NewGuid().ToString() + '.log') + $line = New-LogEntry -LogMessage 'token=do-not-disclose' -RedactSecrets -RedactionText $Replacement -LogFilePath $path -NoConsole -PassThru + $line | Should -Not -Match 'do-not-disclose' + $line.EndsWith($Replacement) | Should -BeTrue + (Get-Content -LiteralPath $path) | Should -Be $line + New-LogEntry -LogMessage 'token=do-not-disclose' -BufferOnly -RedactPattern '(?token=\S+)' -RedactionText $Replacement + (New-LogEntry -GetBuffer).EndsWith($Replacement) | Should -BeTrue + } + + It 'rejects conflicting buffered severity switches: and ' -ForEach @( + @{ First = 'BufferOnlyInfo'; Second = 'BufferOnlyWarning' } + @{ First = 'BufferOnlyInfo'; Second = 'BufferOnlyError' } + @{ First = 'BufferOnlyWarning'; Second = 'BufferOnlyError' } + ) { + $flags = @{ $First = $true; $Second = $true } + { New-LogEntry -LogMessage 'conflict' @flags } | Should -Throw '*Use only one*' + @(New-LogEntry -GetBuffer).Count | Should -Be 0 + } + + It 'still accepts individual buffered severity switches: ' -ForEach @( + @{ Flag = 'BufferOnlyInfo'; Tag = 'INFO' } + @{ Flag = 'BufferOnlyWarning'; Tag = 'WARNING' } + @{ Flag = 'BufferOnlyError'; Tag = 'ERROR' } + ) { + $flags = @{ $Flag = $true } + New-LogEntry -LogMessage 'message' -BufferOnly @flags + (New-LogEntry -GetBuffer) | Should -Match "\[$Tag\]: message$" + } + + It 'preserves an entry appended while the snapshot is being written' { + InModuleScope StandaloneNewLogEntry { + Mock Write-NewLogEntryLines { + [System.Threading.Monitor]::IsEntered($script:NewLogEntryBufferLock) | Should -BeTrue + Add-NewLogEntryBuffer -Lines 'arrived-during-flush' + } + New-LogEntry -LogMessage 'original' -BufferOnly + $flushed = @(New-LogEntry -FlushBuffer -LogFilePath 'unused.log' -NoConsole -PassThru) + $flushed.Count | Should -Be 1 + $flushed[0] | Should -Match ': original$' + @(New-LogEntry -GetBuffer).Count | Should -Be 1 + (New-LogEntry -GetBuffer) | Should -Be 'arrived-during-flush' + $script:messageBuffer | Should -Be ("arrived-during-flush" + [Environment]::NewLine) + } + } + + It 'retains all entries when writing fails and releases the buffer lock' { + InModuleScope StandaloneNewLogEntry { + Mock Write-NewLogEntryLines { throw 'write failed' } + New-LogEntry -LogMessage 'retry-me' -BufferOnly + { New-LogEntry -FlushBuffer -LogFilePath 'unused.log' -NoConsole } | Should -Throw '*write failed*' + (New-LogEntry -GetBuffer) | Should -Match ': retry-me$' + [System.Threading.Monitor]::IsEntered($script:NewLogEntryBufferLock) | Should -BeFalse + New-LogEntry -LogMessage 'next' -BufferOnly + @(New-LogEntry -GetBuffer).Count | Should -Be 2 + } + } + + It 'writes buffered entries exactly once across successive flushes' { + $path = Join-Path $TestDrive 'once.log' + New-LogEntry -LogMessage 'once' -BufferOnly + New-LogEntry -FlushBuffer -LogFilePath $path -NoConsole + New-LogEntry -FlushBuffer -LogFilePath $path -NoConsole + @(Get-Content -LiteralPath $path).Count | Should -Be 1 + @(New-LogEntry -GetBuffer).Count | Should -Be 0 + } + + It 'resolves interactive defaults in the current directory' { + InModuleScope StandaloneNewLogEntry -Parameters @{ Directory = $TestDrive } { + param($Directory) + Push-Location $Directory + try { + $path = Resolve-NewLogEntryPath + (Split-Path $path -Parent) | Should -Be $Directory + (Split-Path $path -Leaf) | Should -Match '^PowerShell-LogFile-\d{8}-\d{6}\.log$' + } + finally { Pop-Location } + } + } + + It 'writes default logs beside a calling script, including buffer flushes' { + $scriptDirectory = Join-Path $TestDrive 'caller' + New-Item -ItemType Directory $scriptDirectory | Out-Null + $caller = Join-Path $scriptDirectory 'automation.ps1' + @' +New-LogEntry -LogMessage 'direct-default' -NoConsole +New-LogEntry -LogMessage 'buffer-default' -BufferOnly +New-LogEntry -FlushBuffer -NoConsole +'@ | Set-Content -LiteralPath $caller + & $caller + $logs = @(Get-ChildItem $scriptDirectory -Filter 'automation.ps1-LogFile-*.log') + $logs.Count | Should -BeGreaterThan 0 + $lines = @(Get-Content -LiteralPath $logs.FullName) + $lines.Count | Should -Be 2 + $lines[0] | Should -Match ': direct-default$' + $lines[1] | Should -Match ': buffer-default$' + } +} + + +Describe 'New-LogEntry file-write integration' { + BeforeEach { New-LogEntry -ClearBuffer } + + It 'can retry buffered entries after a real filesystem failure' { + New-LogEntry -LogMessage 'retained-after-failure' -BufferOnly + { New-LogEntry -FlushBuffer -LogFilePath $TestDrive -NoConsole -ErrorAction Stop } | Should -Throw + $before = @(New-LogEntry -GetBuffer) + $before.Count | Should -Be 1 + $path = Join-Path $TestDrive 'retried.log' + $written = @(New-LogEntry -FlushBuffer -LogFilePath $path -NoConsole -PassThru) + $written[0] | Should -Be $before[0] + (Get-Content -LiteralPath $path) | Should -Be $before[0] + @(New-LogEntry -GetBuffer).Count | Should -Be 0 + } + + It 'preserves every direct and buffered line from concurrent processes' { + $path = Join-Path $TestDrive 'concurrent.log' + $manifest = $loaderPath + $jobs = @() + try { + $jobs = @(1..3 | ForEach-Object { + Start-Job -ArgumentList $manifest, $path, $_ -ScriptBlock { + param($Manifest, $Path, $Worker) + . $Manifest + foreach ($i in 1..20) { + New-LogEntry -LogMessage "worker-$Worker-direct-$i" -LogFilePath $Path -NoConsole -ErrorAction Stop + New-LogEntry -LogMessage "worker-$Worker-buffer-$i" -BufferOnly -ErrorAction Stop + } + New-LogEntry -FlushBuffer -LogFilePath $Path -NoConsole -ErrorAction Stop + } + }) + $jobs | Receive-Job -Wait -ErrorAction Stop + foreach ($job in $jobs) { $job.State | Should -Be 'Completed' } + $lines = @(Get-Content -LiteralPath $path) + $lines.Count | Should -Be 120 + $messages = @($lines | ForEach-Object { $_ -replace '^.*\[INFO\]: ', '' }) + @($messages | Select-Object -Unique).Count | Should -Be 120 + foreach ($worker in 1..3) { + foreach ($i in 1..20) { + $messages | Should -Contain "worker-$worker-direct-$i" + $messages | Should -Contain "worker-$worker-buffer-$i" + } + } + } + finally { + if ($jobs.Count -gt 0) { $jobs | Remove-Job -Force } + } + } +} + + +Describe 'Interactive default log path' { + It 'writes in the current directory when called without a script file' { + $manifest = $loaderPath + $job = Start-Job -ArgumentList $manifest, $TestDrive -ScriptBlock { + param($Manifest, $Directory) + . $Manifest + Set-Location $Directory + New-LogEntry -LogMessage 'interactive-default' -NoConsole -ErrorAction Stop + New-LogEntry -LogMessage 'interactive-buffer' -BufferOnly + New-LogEntry -FlushBuffer -NoConsole -ErrorAction Stop + } + try { + $job | Receive-Job -Wait -ErrorAction Stop + $job.State | Should -Be 'Completed' + $logs = @(Get-ChildItem $TestDrive -Filter 'PowerShell-LogFile-*.log') + $logs.Count | Should -BeGreaterThan 0 + $lines = @(Get-Content -LiteralPath $logs.FullName) + $lines.Count | Should -Be 2 + $lines[0] | Should -Match ': interactive-default$' + $lines[1] | Should -Match ': interactive-buffer$' + } + finally { $job | Remove-Job -Force } + } +} diff --git a/New-StringConversion.ps1 b/New-StringConversion.ps1 index 64a6c79..31e2605 100644 --- a/New-StringConversion.ps1 +++ b/New-StringConversion.ps1 @@ -3,39 +3,39 @@ <# .SYNOPSIS Function to remove any non-unicode character from a string. - + .DESCRIPTION Function is used to sanitize non-unicode characters from a string. - - Function supports custom characters map via the -UnicodeHashTable parameter accepting an hashtable of characters to replace. - - If characters not specified in the default character map are found they are replace with a question mark '?' unless a custom + + Function supports custom characters map via the -UnicodeHashTable parameter accepting an hashtable of characters to replace. + + If characters not specified in the default character map are found they are replace with a question mark '?' unless a custom unknown character is specified via -UnknownCharacter parameter. - + .PARAMETER StringToConvert A string containing characters that need to be sanitized/converted. - + .PARAMETER UnicodeHashTable An hashtable containing characters that should be replaced if parameter is not specified default values will be used. - + .PARAMETER IgnoreSpaces By default spaces will be replaced with a dash '-' sign if paramter is specified function will not convert/take into consideraiotn spaces in the string. - + .PARAMETER RemoveSpaces If parameter is specified spaces will be removed from input string. - + .PARAMETER ReplaceSpaces By default spaces will be replaced with a dash '-' sign if parameter is specified it is possible to specify character to use when a space is encountered in the string. - + .PARAMETER UnknownCharacter By default any special character not found in the UnicodeHashTable will be replaced with a question mark when parameter is used it is possible to specify which character will be used for unknown entries. - + .EXAMPLE PS C:\> New-StringConversion - + .NOTES Additional information about the function. #> @@ -73,18 +73,18 @@ [string] $UnknownCharacter = '?' ) - + begin { # Declare control variable [bool]$isUpperCase = $false - + # Check if we should use custom array hash if (-not ($PSBoundParameters.ContainsKey('UnicodeHashTable'))) { # Hashtable contaning special characters to replace [hashtable]$unicodeHashTable = @{ - + # a 'æ' = 'a' 'à' = 'a' @@ -96,14 +96,14 @@ 'ą' = 'a' 'ä' = 'a' 'á' = 'a' - + # b 'ƀ' = 'b' 'ƃ' = 'b' - + # Tone six 'ƅ' = 'b' - + # c 'ç' = 'c' 'ć' = 'c' @@ -111,12 +111,12 @@ 'ċ' = 'c' 'č' = 'c' 'ƈ' = 'c' - + # d 'ď' = 'd' 'đ' = 'd' 'ƌ' = 'd' - + # e 'è' = 'e' 'é' = 'e' @@ -128,17 +128,17 @@ 'ę' = 'e' 'ě' = 'e' '&' = 'e' - + # g 'ĝ' = 'e' 'ğ' = 'e' 'ġ' = 'e' 'ģ' = 'e' - + # h 'ĥ' = 'h' 'ħ' = 'h' - + # i 'ì' = 'i' 'í' = 'i' @@ -149,22 +149,22 @@ 'ĭ' = 'i' 'į' = 'i' 'ı' = 'i' - + # j 'ij' = 'j' 'ĵ' = 'j' - + # k 'ķ' = 'k' 'ĸ' = 'k' - + # l 'ĺ' = 'l' 'ļ' = 'l' 'ľ' = 'l' 'ŀ' = 'l' 'ł' = 'l' - + # n 'ñ' = 'n' 'ń' = 'n' @@ -172,7 +172,7 @@ 'ň' = 'n' 'ʼn' = 'n' 'ŋ' = 'n' - + # o 'ð' = 'o' 'ó' = 'o' @@ -184,12 +184,12 @@ 'ŏ' = 'o' 'ő' = 'o' 'œ' = 'o' - + # r 'ŕ' = 'r' 'ŗ' = 'r' 'ř' = 'r' - + # s 'ś' = 's' 'ŝ' = 's' @@ -197,12 +197,12 @@ 'š' = 's' 'ß' = 'ss' 'ſ' = 's' - + # t 'ţ' = 't' 'ť' = 't' 'ŧ' = 't' - + # u 'ù' = 'u' 'ú' = 'u' @@ -214,49 +214,49 @@ 'ů' = 'u' 'ű' = 'u' 'ų' = 'u' - + # w 'ŵ' = 'w' - + # y 'ý' = 'y' 'ÿ' = 'y' 'ŷ' = 'y' - + # z 'ź' = 'z' 'ż' = 'z' 'ž' = 'z' } } - + switch ($PSBoundParameters.Keys) { 'IgnoreSpaces' { $UnicodeHashTable.Add(' ', ' ') - + break } 'ReplaceSpaces' { # Replace spaces with specified character $UnicodeHashTable.Add(' ', $ReplaceSpaces) - + break } 'RemoveSpaces' { # Replace spaces with specified character $UnicodeHashTable.Add(' ', '') - + break } } - + # Create new chararray [System.Collections.ArrayList]$resultStringArray = @() - + # Set a regex for additional special characters [string]$unicodeRegExString = "^([0-9a-zA-Z!#$@.'^_`~-])*$" } @@ -264,35 +264,35 @@ { # Convert string to array [array]$stringCharArray = $StringToConvert.ToCharArray() - + foreach ($character in $stringCharArray) { # Reset control variables $isUpperCase = $false - + # Set Char ref with current value [string]$currentChar = $character.ToString() [string]$currentCharLower = $character.ToString().ToLower() - + # Get character case if ($currentChar.CompareTo($currentCharLower) -eq 1) { $isUpperCase = $true } - + # Check if character should be translated if ($UnicodeHashTable.ContainsKey($currentCharLower) -eq $true) { # Get unicode equivalent [string]$tmpChar = $UnicodeHashTable[$currentChar] - + # Set character case switch ($isUpperCase) { $true { $resultStringArray.Add($tmpChar.ToUpper()) - + break } default @@ -308,7 +308,7 @@ { # Handle characters not in hash $currentChar = $UnknownCharacter - + # Append to result array $resultStringArray.Add($currentChar).ToString() } diff --git a/New-Timer.ps1 b/New-Timer.ps1 index 9f5bf9a..62bcb39 100644 --- a/New-Timer.ps1 +++ b/New-Timer.ps1 @@ -1,23 +1,23 @@ function New-Timer { - <# - .SYNOPSIS - Creates a new stopwatch. - - .DESCRIPTION - Function will create a new time, using the StopWatch class, allowing measurement of elapsed time in scripts. - - .EXAMPLE - PS C:\> New-Timer - - .NOTES - Function takes no parameters and will start a new StopWatch object. - #> - - [OutputType([System.Diagnostics.Stopwatch])] - param () - - $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() - - return $stopwatch + <# + .SYNOPSIS + Creates a new stopwatch. + + .DESCRIPTION + Function will create a new time, using the StopWatch class, allowing measurement of elapsed time in scripts. + + .EXAMPLE + PS C:\> New-Timer + + .NOTES + Function takes no parameters and will start a new StopWatch object. + #> + + [OutputType([System.Diagnostics.Stopwatch])] + param () + + $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() + + return $stopwatch } \ No newline at end of file diff --git a/Stop-Timer.ps1 b/Stop-Timer.ps1 index eebccda..c480b5f 100644 --- a/Stop-Timer.ps1 +++ b/Stop-Timer.ps1 @@ -1,73 +1,73 @@ function Stop-Timer { - <# - .SYNOPSIS - Function will halt a stopwatch. - - .DESCRIPTION - Function requires a [System.Diagnostics.Stopwatch] object as input and will invoke the stop() method to hald its execution. - - If no exceptions are returned function will return $True. - - .PARAMETER Timer - A [System.Diagnostics.Stopwatch] representing the stopwatch to stop. - - .EXAMPLE - PS C:\> Stop-Timer -Timer $Timer - - .OUTPUTS - System.Boolean - #> - - [OutputType([bool])] - param - ( - [Parameter(Mandatory = $true)] - [System.Diagnostics.Stopwatch]$Timer - ) - - Begin - { - # Save current configuration - [string]$currentConfig = $ErrorActionPreference - - # Update configuration - $ErrorActionPreference = 'Stop' - } - - Process - { - try - { - # Stop timer - $Timer.Stop() - - return $true - } - catch - { - # Save exception - [string]$reportedException = $Error[0].Exception.Message - - Write-Warning -Message 'Exception reported while halting stopwatch - Use the -Verbose parameter for more details' - - # Check we have an exception message - if ([string]::IsNullOrEmpty($reportedException) -eq $false) - { - Write-Verbose -Message $reportedException - } - else - { - Write-Verbose -Message 'No inner exception reported by Disconnect-AzureAD cmdlet' - } - - return $false - } - } - - End - { - # Revert back configuration - $ErrorActionPreference = $currentConfig - } + <# + .SYNOPSIS + Function will halt a stopwatch. + + .DESCRIPTION + Function requires a [System.Diagnostics.Stopwatch] object as input and will invoke the stop() method to hald its execution. + + If no exceptions are returned function will return $True. + + .PARAMETER Timer + A [System.Diagnostics.Stopwatch] representing the stopwatch to stop. + + .EXAMPLE + PS C:\> Stop-Timer -Timer $Timer + + .OUTPUTS + System.Boolean + #> + + [OutputType([bool])] + param + ( + [Parameter(Mandatory = $true)] + [System.Diagnostics.Stopwatch]$Timer + ) + + Begin + { + # Save current configuration + [string]$currentConfig = $ErrorActionPreference + + # Update configuration + $ErrorActionPreference = 'Stop' + } + + Process + { + try + { + # Stop timer + $Timer.Stop() + + return $true + } + catch + { + # Save exception + [string]$reportedException = $Error[0].Exception.Message + + Write-Warning -Message 'Exception reported while halting stopwatch - Use the -Verbose parameter for more details' + + # Check we have an exception message + if ([string]::IsNullOrEmpty($reportedException) -eq $false) + { + Write-Verbose -Message $reportedException + } + else + { + Write-Verbose -Message 'No inner exception reported by Disconnect-AzureAD cmdlet' + } + + return $false + } + } + + End + { + # Revert back configuration + $ErrorActionPreference = $currentConfig + } } \ No newline at end of file diff --git a/Test-IsEmail.ps1 b/Test-IsEmail.ps1 index 857b319..2dc7295 100644 --- a/Test-IsEmail.ps1 +++ b/Test-IsEmail.ps1 @@ -1,49 +1,49 @@ function Test-IsEmail { - <# - .SYNOPSIS - Function to check if a string is an RFC email address. - - .DESCRIPTION - Function will check if an input string is an RFC complient email address. - - .PARAMETER EmailAddress - A string representing the email address to be checked - - .EXAMPLE - PS C:\> Test-IsEmail -EmailAddress 'value1' - - .OUTPUTS - System.Boolean - - .LINK - Restrictions on email addresses - https://tools.ietf.org/html/rfc3696#section-3 - #> - - [OutputType([bool])] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [Alias('Email', 'Mail', 'Address')] - [string] - $EmailAddress - ) - - try - { - # Check if address is RFC compliant - [void]([mailaddress]$EmailAddress) - - Write-Verbose -Message "Address $EmailAddress is an RFC compliant address" - - return $true - } - catch - { - Write-Verbose -Message "Address $EmailAddress is not an RFC compliant address" - - return $false - } + <# + .SYNOPSIS + Function to check if a string is an RFC email address. + + .DESCRIPTION + Function will check if an input string is an RFC complient email address. + + .PARAMETER EmailAddress + A string representing the email address to be checked + + .EXAMPLE + PS C:\> Test-IsEmail -EmailAddress 'value1' + + .OUTPUTS + System.Boolean + + .LINK + Restrictions on email addresses + https://tools.ietf.org/html/rfc3696#section-3 + #> + + [OutputType([bool])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [Alias('Email', 'Mail', 'Address')] + [string] + $EmailAddress + ) + + try + { + # Check if address is RFC compliant + [void]([mailaddress]$EmailAddress) + + Write-Verbose -Message "Address $EmailAddress is an RFC compliant address" + + return $true + } + catch + { + Write-Verbose -Message "Address $EmailAddress is not an RFC compliant address" + + return $false + } } diff --git a/Test-IsGuid.ps1 b/Test-IsGuid.ps1 index 6d53e18..c6ab6e2 100644 --- a/Test-IsGuid.ps1 +++ b/Test-IsGuid.ps1 @@ -1,44 +1,44 @@ -function Test-IsGuid -{ - <# - .SYNOPSIS - Cmdlet will check if input string is a valid GUID. - - .DESCRIPTION - Cmdlet will check if input string is a valid GUID. - - .PARAMETER ObjectGuid - A string representing the GUID to be tested. - - .EXAMPLE - PS C:\> Test-IsGuid -ObjectGuid 'value1' - - # Output - $False - - .EXAMPLE - PS C:\> Test-IsGuid -ObjectGuid '7761bf39-9a9f-42c8-869f-7c6e2689811a' - - # Output - $True - - .OUTPUTS - System.Boolean - - .NOTES - Additional information about the function. - #> - [OutputType([bool])] - param - ( - [Parameter(Mandatory = $true)] - [string] - $ObjectGuid - ) - - # Define verification regex - [regex]$guidRegex = '(?im)^[{(]?[0-9A-F]{8}[-]?(?:[0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$' - - # Check guid against regex - return $ObjectGuid -match $guidRegex -} +function Test-IsGuid +{ + <# + .SYNOPSIS + Cmdlet will check if input string is a valid GUID. + + .DESCRIPTION + Cmdlet will check if input string is a valid GUID. + + .PARAMETER ObjectGuid + A string representing the GUID to be tested. + + .EXAMPLE + PS C:\> Test-IsGuid -ObjectGuid 'value1' + + # Output + $False + + .EXAMPLE + PS C:\> Test-IsGuid -ObjectGuid '7761bf39-9a9f-42c8-869f-7c6e2689811a' + + # Output + $True + + .OUTPUTS + System.Boolean + + .NOTES + Additional information about the function. + #> + [OutputType([bool])] + param + ( + [Parameter(Mandatory = $true)] + [string] + $ObjectGuid + ) + + # Define verification regex + [regex]$guidRegex = '(?im)^[{(]?[0-9A-F]{8}[-]?(?:[0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$' + + # Check guid against regex + return $ObjectGuid -match $guidRegex +} diff --git a/Test-IsRegistryKey.ps1 b/Test-IsRegistryKey.ps1 index 8054132..c2e8a63 100644 --- a/Test-IsRegistryKey.ps1 +++ b/Test-IsRegistryKey.ps1 @@ -3,17 +3,17 @@ function Test-IsRegistryKey <# .SYNOPSIS Cmdlet will check if the specified registry key is valid. - + .DESCRIPTION Cmdlet will check if the specified registry path is valid. - + .PARAMETER KeyPath A string representing the registry path to check in the PSDrive format IE HKLM:\SOFTWARE - + .EXAMPLE PS C:\> Test-IsRegistryKey -KeyPath 'value1' #> - + [OutputType([bool])] param ( @@ -22,7 +22,7 @@ function Test-IsRegistryKey [string] $KeyPath ) - + if (Test-Path -Path $KeyPath) { return (Get-Item -Path $KeyPath).PsProvider.Name -match 'Registry' diff --git a/Test-IsValidDn.ps1 b/Test-IsValidDn.ps1 index 25a53c4..1d072b1 100644 --- a/Test-IsValidDn.ps1 +++ b/Test-IsValidDn.ps1 @@ -3,19 +3,19 @@ function Test-IsValidDN <# .SYNOPSIS Cmdlet will check if the input string is a valid distinguishedname. - + .DESCRIPTION Cmdlet will check if the input string is a valid distinguishedname. - + Cmdlet is intended as a dignostic tool for input validation - + .PARAMETER ObjectDN A string representing the object distinguishedname. - + .EXAMPLE PS C:\> Test-IsValidDN -ObjectDN 'Value1' #> - + [OutputType([bool])] param ( @@ -25,7 +25,7 @@ function Test-IsValidDN [string] $ObjectDN ) - + # Create new string builder [System.Text.StringBuilder]$regexStringBuilder = [System.Text.StringBuilder]::New() [void]($regexStringBuilder.Append('^(?:[A-Za-z][\w-]*|\d+(?:\.\d+)*)=(?:#(?:[\dA-Fa-f]{2})+|')) @@ -38,9 +38,9 @@ function Test-IsValidDN [void]($regexStringBuilder.Append('{2})*|"(?:[^\\"]|\\[,=\+<>#;\\"]|\\[\dA-Fa-f]{2})*")(?:\+(?:[A-Za-z]')) [void]($regexStringBuilder.Append('[\w-]*|\d+(?:\.\d+)*)=(?:#(?:[\dA-Fa-f]{2})+|(?:[^,=\+<>#;\\"]|\\[,=\')) [void]($regexStringBuilder.Append('+<>#;\\"]|\\[\dA-Fa-f]{2})*|"(?:[^\\"]|\\[,=\+<>#;\\"]|\\[\dA-Fa-f]{2})*"))*)*$')) - + # Define DN Regex [string]$distinguishedNameRegex = $regexStringBuilder.ToString() - + return $ObjectDN -match $distinguishedNameRegex } \ No newline at end of file diff --git a/Test-UpnExist.ps1 b/Test-UpnExist.ps1 index e21a2a8..d1d8775 100644 --- a/Test-UpnExist.ps1 +++ b/Test-UpnExist.ps1 @@ -3,21 +3,21 @@ function Test-UPNExist <# .SYNOPSIS Cmdlet will check if a given UPN exists in the forest. - + .DESCRIPTION Cmdlet is a diagnostic tool to check if a given UPN is already assigned to a user in the forest. - + .PARAMETER UPN A string representing the UPN to check for uniqueness. - + .PARAMETER AdServer A string representing the name of the domain controller to be used for the check, if parameter is not specified the closest Global Catalog is used. - + .EXAMPLE PS C:\> Test-UPNExist -UPN 'John.Doe@example.com' #> - + [CmdletBinding()] param ( @@ -27,7 +27,7 @@ function Test-UPNExist [ValidateNotNullOrEmpty()] [string]$AdServer ) - + if ([string]::IsNullOrEmpty($AdServer) -eq $true) { $adForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() @@ -37,7 +37,7 @@ function Test-UPNExist { [string]$ldapPath = '{0}{1}' -f 'LDAP://', $AdServer } - + # Instantiate required objects and run query $adDomain = New-Object System.DirectoryServices.DirectoryEntry($ldapPath) $adSearcher = New-Object System.DirectoryServices.DirectorySearcher($adDomain) @@ -45,8 +45,8 @@ function Test-UPNExist $adSearcher.PageSize = 1000 $adSearcher.Filter = "(&(objectCategory=person)(userPrincipalName=$UPN))" [void]($adSearcher.PropertiesToLoad.Add("userPrincipalName")) - + [array]$searchResult = $adSearcher.FindOne() - + return $null -ne $searchResult } \ No newline at end of file