Utilities.psm1
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Contains long links.')] [CmdletBinding()] param() if ($PSVersionTable.PSVersion -lt '6.0') { [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSAvoidAssignmentToAutomaticVariable', '', Justification = 'Compatibility with PowerShell 6.0 and newer.' )] $IsWindows = [System.Environment]::OSVersion.Platform -eq 'Win32NT' } $scriptName = 'Utilities' Write-Verbose "[$scriptName] - Importing module" #region - From [public] Write-Verbose "[$scriptName] - [public] - Processing folder" #region - From [public] - [Base64] Write-Verbose "[$scriptName] - [public] - [Base64] - Processing folder" #region - From [public] - [Base64] - [ConvertFrom-Base64String] Write-Verbose "[$scriptName] - [public] - [Base64] - [ConvertFrom-Base64String] - Importing" filter ConvertFrom-Base64String { <# .SYNOPSIS Convert to string from base64 .DESCRIPTION Convert to string from base64 .EXAMPLE ConvertFrom-Base64String -String 'SABlAGwAbABvACAAVwBvAHIAbABkAA==' Hello World Converts the string from base64 to a regular string. #> [CmdletBinding()] param ( # The string to convert from base64 [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )] [string] $String ) $ConvertedString = [System.Convert]::FromBase64String($String) $DecodedText = [System.Text.Encoding]::Unicode.GetString($ConvertedString) $DecodedText } Write-Verbose "[$scriptName] - [public] - [Base64] - [ConvertFrom-Base64String] - Done" #endregion - From [public] - [Base64] - [ConvertFrom-Base64String] #region - From [public] - [Base64] - [ConvertTo-Base64String] Write-Verbose "[$scriptName] - [public] - [Base64] - [ConvertTo-Base64String] - Importing" filter ConvertTo-Base64String { <# .SYNOPSIS Convert a string to base64 .DESCRIPTION Convert a string to base64 .EXAMPLE 'Hello World' | ConvertTo-Base64String SABlAGwAbABvACAAVwBvAHIAbABkAA== Converts the string 'Hello World' to base64. #> [OutputType([string])] [CmdletBinding()] param( # The string to convert to base64 [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )] [string] $String ) $bytes = [System.Text.Encoding]::Unicode.GetBytes($String) $encodedText = [System.Convert]::ToBase64String($bytes) #$ADOToken = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$($PAT)")) $encodedText } Write-Verbose "[$scriptName] - [public] - [Base64] - [ConvertTo-Base64String] - Done" #endregion - From [public] - [Base64] - [ConvertTo-Base64String] Write-Verbose "[$scriptName] - [public] - [Base64] - Done" #endregion - From [public] - [Base64] #region - From [public] - [Boolean] Write-Verbose "[$scriptName] - [public] - [Boolean] - Processing folder" #region - From [public] - [Boolean] - [ConvertTo-Boolean] Write-Verbose "[$scriptName] - [public] - [Boolean] - [ConvertTo-Boolean] - Importing" filter ConvertTo-Boolean { <# .SYNOPSIS Convert string to boolean. .DESCRIPTION Convert string to boolean. .EXAMPLE ConvertTo-Boolean -String 'true' True Convert string to boolean. #> [OutputType([bool])] [CmdletBinding()] param( # The string to be converted to boolean. [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )] [string] $String ) switch -regex ($String.Trim()) { '^(1|true|yes|on|enabled)$' { $true } default { $false } } } Write-Verbose "[$scriptName] - [public] - [Boolean] - [ConvertTo-Boolean] - Done" #endregion - From [public] - [Boolean] - [ConvertTo-Boolean] Write-Verbose "[$scriptName] - [public] - [Boolean] - Done" #endregion - From [public] - [Boolean] #region - From [public] - [Files] Write-Verbose "[$scriptName] - [public] - [Files] - Processing folder" #region - From [public] - [Files] - [Get-FileInfo] Write-Verbose "[$scriptName] - [public] - [Files] - [Get-FileInfo] - Importing" function Get-FileInfo { <# .SYNOPSIS Get file information .DESCRIPTION Get file information .EXAMPLE Get-FileInfo -Path 'C:\temp\test.txt' Gets detailed information about the file. .NOTES Supported OS: Windows #> [OutputType([pscustomobject])] [CmdletBinding()] param ( # The path to the file. [Parameter(Mandatory)] [string] $Path ) if (-not (Test-Path -Path $Path)) { Write-Error 'Path does not exist' -ErrorAction Stop } $Item = Get-Item -Path $Path #If item is directory, fail if ($Item.PSIsContainer) { Write-Error 'Path is a directory' -ErrorAction Stop } $shell = New-Object -ComObject Shell.Application $shellFolder = $shell.Namespace($Item.Directory.FullName) $shellFile = $shellFolder.ParseName($Item.name) $fileDetails = New-Object pscustomobject foreach ($i in 0..1000) { $propertyName = $shellfolder.GetDetailsOf($null, $i) $propertyValue = $shellfolder.GetDetailsOf($shellfile, $i) if (-not [string]::IsNullOrEmpty($propertyValue)) { Write-Verbose "[$propertyName] - [$propertyValue]" $fileDetails | Add-Member -MemberType NoteProperty -Name $propertyName -Value $propertyValue } } return $fileDetails } Write-Verbose "[$scriptName] - [public] - [Files] - [Get-FileInfo] - Done" #endregion - From [public] - [Files] - [Get-FileInfo] #region - From [public] - [Files] - [Remove-EmptyFolder] Write-Verbose "[$scriptName] - [public] - [Files] - [Remove-EmptyFolder] - Importing" function Remove-EmptyFolder { <# .SYNOPSIS Removes empty folders under the folder specified .DESCRIPTION Removes empty folders under the folder specified .EXAMPLE Remove-EmptyFolder -Path . Removes empty folders under the current path and outputs the results to the console. #> [OutputType([void])] [CmdletBinding(SupportsShouldProcess)] param( # The path to the folder to be cleaned [Parameter(Mandatory)] [string] $Path ) Get-ChildItem -Path $Path -Recurse -Directory | ForEach-Object { if ($null -eq (Get-ChildItem $_.FullName -Force -Recurse)) { Write-Verbose "Removing empty folder: [$($_.FullName)]" if ($PSCmdlet.ShouldProcess("folder [$($_.FullName)]", 'Remove')) { Remove-Item $_.FullName -Force } } } } Write-Verbose "[$scriptName] - [public] - [Files] - [Remove-EmptyFolder] - Done" #endregion - From [public] - [Files] - [Remove-EmptyFolder] #region - From [public] - [Files] - [Show-FileContent] Write-Verbose "[$scriptName] - [public] - [Files] - [Show-FileContent] - Importing" function Show-FileContent { <# .SYNOPSIS Prints the content of a file with line numbers in front of each line. .DESCRIPTION Prints the content of a file with line numbers in front of each line. .EXAMPLE $Path = 'C:\Repos\GitHub\PSModule\Framework\PSModule.FX\src\PSModule.FX\private\Utilities\Show-FileContent.ps1' Show-FileContent -Path $Path Shows the content of the file with line numbers in front of each line. #> [CmdletBinding()] param ( # The path to the file to show the content of. [Parameter(Mandatory)] [string]$Path ) $content = Get-Content -Path $Path $lineNumber = 1 $columnSize = $content.Count.ToString().Length # Foreach line print the line number in front of the line with [ ] around it. # The linenumber should dynamically adjust to the number of digits with the length of the file. foreach ($line in $content) { $lineNumberFormatted = $lineNumber.ToString().PadLeft($columnSize) '[{0}] {1}' -f $lineNumberFormatted, $line $lineNumber++ } } Write-Verbose "[$scriptName] - [public] - [Files] - [Show-FileContent] - Done" #endregion - From [public] - [Files] - [Show-FileContent] Write-Verbose "[$scriptName] - [public] - [Files] - Done" #endregion - From [public] - [Files] #region - From [public] - [Git] Write-Verbose "[$scriptName] - [public] - [Git] - Processing folder" #region - From [public] - [Git] - [Clear-GitRepo] Write-Verbose "[$scriptName] - [public] - [Git] - [Clear-GitRepo] - Importing" function Clear-GitRepo { <# .SYNOPSIS Clear a Git repository of all branches except main .DESCRIPTION Clear a Git repository of all branches except main .EXAMPLE Clear-GitRepo Clear a Git repository of all branches except main #> [OutputType([void])] [CmdletBinding()] param() git fetch --all --prune (git branch).Trim() | Where-Object { $_ -notmatch 'main|\*' } | ForEach-Object { git branch $_ --delete --force } } Write-Verbose "[$scriptName] - [public] - [Git] - [Clear-GitRepo] - Done" #endregion - From [public] - [Git] - [Clear-GitRepo] #region - From [public] - [Git] - [Invoke-GitSquash] Write-Verbose "[$scriptName] - [public] - [Git] - [Invoke-GitSquash] - Importing" function Invoke-GitSquash { <# .SYNOPSIS Squash all commits on a branch into a single commit .DESCRIPTION Squash all commits on a branch into a single commit .EXAMPLE Invoke-GitSquash Squash all commits on a branch into a single commit #> [OutputType([void])] [CmdletBinding()] [Alias('Squash-Main')] param( # The commit message to use for the squashed commit [Parameter()] [string] $CommitMessage = 'Squash', # The branch to squash [Parameter()] [string] $BranchName = 'main', # Temporary branch name [Parameter()] [string] $TempBranchName = 'init' ) git fetch --all --prune $gitHightFrom2ndCommit = [int](git rev-list --count --first-parent $BranchName) - 1 git reset HEAD~$gitHightFrom2ndCommit git checkout -b $TempBranchName git add . git commit -m "$CommitMessage" git push --set-upstream origin $TempBranchName git checkout $BranchName git push --force git checkout $TempBranchName } Write-Verbose "[$scriptName] - [public] - [Git] - [Invoke-GitSquash] - Done" #endregion - From [public] - [Git] - [Invoke-GitSquash] #region - From [public] - [Git] - [Invoke-SquashBranch] Write-Verbose "[$scriptName] - [public] - [Git] - [Invoke-SquashBranch] - Importing" function Invoke-SquashBranch { <# .SYNOPSIS Squash a branch to a single commit .DESCRIPTION Squash a branch to a single commit .EXAMPLE Invoke-SquashBranch #> [Alias('Squash-Branch')] [CmdletBinding()] param( # The name of the branch to squash [Parameter()] [string] $BranchName = 'main' ) git reset $(git merge-base $BranchName $(git branch --show-current)) } Write-Verbose "[$scriptName] - [public] - [Git] - [Invoke-SquashBranch] - Done" #endregion - From [public] - [Git] - [Invoke-SquashBranch] #region - From [public] - [Git] - [Reset-GitRepo] Write-Verbose "[$scriptName] - [public] - [Git] - [Reset-GitRepo] - Importing" function Reset-GitRepo { <# .SYNOPSIS Reset a Git repository to the upstream branch .DESCRIPTION Reset a Git repository to the upstream branch .EXAMPLE Reset-GitRepo Reset a Git repository to the upstream branch #> [OutputType([void])] [CmdletBinding(SupportsShouldProcess)] param( # The upstream repository to reset to [Parameter()] [string] $Upstream = 'upstream', # The branch to reset [Parameter()] [string] $Branch = 'main', # Whether to push the reset [Parameter()] [switch] $Push ) git fetch $Upstream git checkout $Branch if ($PSCmdlet.ShouldProcess("git repo", "Reset")) { git reset --hard $Upstream/$Branch } if ($Push) { if ($PSCmdlet.ShouldProcess("git changes to origin", "Push")) { } git push origin $Branch --force } } Write-Verbose "[$scriptName] - [public] - [Git] - [Reset-GitRepo] - Done" #endregion - From [public] - [Git] - [Reset-GitRepo] #region - From [public] - [Git] - [Restore-GitRepo] Write-Verbose "[$scriptName] - [public] - [Git] - [Restore-GitRepo] - Importing" function Restore-GitRepo { <# .SYNOPSIS Restore a Git repository with upstream .DESCRIPTION Restore a Git repository with upstream .EXAMPLE Restore-GitRepo #> [OutputType([void])] [CmdletBinding()] param( # The name of the branch to squash [Parameter()] [string] $BranchName = 'main' ) git remote add upstream https://github.com/Azure/ResourceModules.git git fetch upstream git restore --source upstream/$BranchName * ':!*global.variables.*' ':!settings.json*' } Write-Verbose "[$scriptName] - [public] - [Git] - [Restore-GitRepo] - Done" #endregion - From [public] - [Git] - [Restore-GitRepo] #region - From [public] - [Git] - [Sync-GitRepo] Write-Verbose "[$scriptName] - [public] - [Git] - [Sync-GitRepo] - Importing" function Sync-GitRepo { <# .SYNOPSIS Sync a Git repository with upstream .DESCRIPTION Sync a Git repository with upstream .EXAMPLE Sync-GitRepo #> [OutputType([void])] [CmdletBinding()] param() git fetch upstream --prune git pull git push } Set-Alias -Name sync -Value Sync-Git Write-Verbose "[$scriptName] - [public] - [Git] - [Sync-GitRepo] - Done" #endregion - From [public] - [Git] - [Sync-GitRepo] #region - From [public] - [Git] - [Sync-Repo] Write-Verbose "[$scriptName] - [public] - [Git] - [Sync-Repo] - Importing" function Sync-Repo { <# .SYNOPSIS Sync a Git repository with upstream .DESCRIPTION Sync a Git repository with upstream .EXAMPLE Sync-Repo #> [OutputType([void])] [CmdletBinding()] param() git checkout main git pull git remote update origin --prune git branch -vv | Select-String -Pattern ': gone]' | ForEach-Object { $_.toString().Trim().Split(' ')[0] } | ForEach-Object { git branch -D $_ } } Write-Verbose "[$scriptName] - [public] - [Git] - [Sync-Repo] - Done" #endregion - From [public] - [Git] - [Sync-Repo] Write-Verbose "[$scriptName] - [public] - [Git] - Done" #endregion - From [public] - [Git] #region - From [public] - [GitHub] Write-Verbose "[$scriptName] - [public] - [GitHub] - Processing folder" #region - From [public] - [GitHub] - [Import-Variable] Write-Verbose "[$scriptName] - [public] - [GitHub] - [Import-Variable] - Importing" filter Import-Variable { <# .SYNOPSIS Import variables from a JSON file into the current session .DESCRIPTION Import variables from a JSON file into the current session .EXAMPLE Import-Variables -Path 'C:\path\to\variables.json' #> [OutputType([void])] [Alias('Import-Variables')] [CmdletBinding()] param ( # Path to the JSON file containing the variables [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )] [string] $Path ) Write-Output "$($MyInvocation.MyCommand) - $Path - Processing" if (-not (Test-Path -Path $Path)) { throw "$($MyInvocation.MyCommand) - $Path - File not found" } $Variables = Get-Content -Path $Path -Raw -Force | ConvertFrom-Json $NestedVariablesFilePaths = ($Variables.PSObject.Properties | Where-Object Name -EQ 'VariablesFilePaths').Value foreach ($NestedVariablesFilePath in $NestedVariablesFilePaths) { Write-Output "$($MyInvocation.MyCommand) - $Path - Nested variable files - $NestedVariablesFilePath" $NestedVariablesFilePath | Import-Variables } Write-Output "$($MyInvocation.MyCommand) - $Path - Loading variables" foreach ($Property in $Variables.PSObject.Properties) { if ($Property -match 'VariablesFilePaths') { continue } Set-GitHubEnv -Name $Property.Name -Value $Property.Value } Write-Output "$($MyInvocation.MyCommand) - $Path - Done" } Write-Verbose "[$scriptName] - [public] - [GitHub] - [Import-Variable] - Done" #endregion - From [public] - [GitHub] - [Import-Variable] #region - From [public] - [GitHub] - [Set-GitHubEnvironmentVariable] Write-Verbose "[$scriptName] - [public] - [GitHub] - [Set-GitHubEnvironmentVariable] - Importing" function Set-GitHubEnvironmentVariable { <# .SYNOPSIS Set a GitHub environment variable .DESCRIPTION Set a GitHub environment variable .EXAMPLE Set-GitHubEnv -Name 'MyVariable' -Value 'MyValue' #> [OutputType([void])] [Alias('Set-GitHubEnv')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', Justification = 'Does not change system state significantly' )] [CmdletBinding()] param ( # Name of the variable [Parameter(Mandatory)] [string] $Name, # Value of the variable [Parameter(Mandatory)] [string] $Value ) Write-Verbose (@{ $Name = $Value } | Format-Table -Wrap -AutoSize | Out-String) Write-Output "$Name=$Value" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append } Write-Verbose "[$scriptName] - [public] - [GitHub] - [Set-GitHubEnvironmentVariable] - Done" #endregion - From [public] - [GitHub] - [Set-GitHubEnvironmentVariable] #region - From [public] - [GitHub] - [Start-LogGroup] Write-Verbose "[$scriptName] - [public] - [GitHub] - [Start-LogGroup] - Importing" function Start-LogGroup { <# .SYNOPSIS Starts a new log group. .DESCRIPTION Starts a new log group. .EXAMPLE New-LogGroup -Name 'MyGroup' .NOTES [Azure DevOps - Formatting commands](https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=bash#formatting-commands) [GitHub - Grouping log lines](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#grouping-log-lines) #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSAvoidUsingWriteHost', '', Justification = 'Write-Host is used to group log messages.' )] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', Justification = 'This function does not change state. It only logs messages.' )] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSAvoidLongLines', '', Justification = 'Contains long links.' )] param( # Name of the log group. [Parameter(Mandatory)] [string] $Name ) if ($env:GITHUB_ACTIONS) { Write-Host "::group::$Name" } elseif ( $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI ) { Write-Host "##[group]$Name" } else { Write-Host "-------- $Name --------" } } Write-Verbose "[$scriptName] - [public] - [GitHub] - [Start-LogGroup] - Done" #endregion - From [public] - [GitHub] - [Start-LogGroup] #region - From [public] - [GitHub] - [Stop-LogGroup] Write-Verbose "[$scriptName] - [public] - [GitHub] - [Stop-LogGroup] - Importing" function Stop-LogGroup { <# .SYNOPSIS Stops a log group. .DESCRIPTION Stops a log group. .EXAMPLE Stop-LogGroup .NOTES [Azure DevOps - Formatting commands](https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=bash#formatting-commands) [GitHub - Grouping log lines](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#grouping-log-lines) #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSAvoidUsingWriteHost', '', Justification = 'Write-Host is used to group log messages.' )] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', Justification = 'This function does not change state. It only logs messages.' )] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSAvoidLongLines', '', Justification = 'Contains long links.' )] param () if ($env:GITHUB_ACTIONS) { Write-Host '::endgroup::' } elseif ( $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI ) { Write-Host '##[endgroup]' } else { Write-Host "-------- $Name --------" } } Write-Verbose "[$scriptName] - [public] - [GitHub] - [Stop-LogGroup] - Done" #endregion - From [public] - [GitHub] - [Stop-LogGroup] Write-Verbose "[$scriptName] - [public] - [GitHub] - Done" #endregion - From [public] - [GitHub] #region - From [public] - [GUID] Write-Verbose "[$scriptName] - [public] - [GUID] - Processing folder" #region - From [public] - [GUID] - [Search-GUID] Write-Verbose "[$scriptName] - [public] - [GUID] - [Search-GUID] - Importing" filter Search-GUID { <# .SYNOPSIS Search a string for a GUID .DESCRIPTION Search a string for a GUID .EXAMPLE '123e4567-e89b-12d3-a456-426655440000' | Search-GUID #> [Cmdletbinding()] [OutputType([guid])] param( # The string to search [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )] [string] $String ) Write-Verbose "Looking for a GUID in $String" $GUID = $String.ToLower() | Select-String -Pattern '[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}' | Select-Object -ExpandProperty Matches | Select-Object -ExpandProperty Value Write-Verbose "Found GUID: $GUID" $GUID } Write-Verbose "[$scriptName] - [public] - [GUID] - [Search-GUID] - Done" #endregion - From [public] - [GUID] - [Search-GUID] #region - From [public] - [GUID] - [Test-IsGUID] Write-Verbose "[$scriptName] - [public] - [GUID] - [Test-IsGUID] - Importing" filter Test-IsGUID { <# .SYNOPSIS Test if a string is a GUID .DESCRIPTION Test if a string is a GUID .EXAMPLE '123e4567-e89b-12d3-a456-426655440000' | Test-IsGUID True #> [Cmdletbinding()] [Alias('IsGUID')] [OutputType([bool])] param ( # The string to test [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )] [string] $String ) [regex]$guidRegex = '(?im)^[{(]?[0-9A-F]{8}[-]?(?:[0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$' # Check GUID against regex $String -match $guidRegex } Write-Verbose "[$scriptName] - [public] - [GUID] - [Test-IsGUID] - Done" #endregion - From [public] - [GUID] - [Test-IsGUID] Write-Verbose "[$scriptName] - [public] - [GUID] - Done" #endregion - From [public] - [GUID] #region - From [public] - [Hashtable] Write-Verbose "[$scriptName] - [public] - [Hashtable] - Processing folder" #region - From [public] - [Hashtable] - [Convert-HashtableToString] Write-Verbose "[$scriptName] - [public] - [Hashtable] - [Convert-HashtableToString] - Importing" function Convert-HashtableToString { <# .SYNOPSIS Converts a hashtable to its code representation. .DESCRIPTION Recursively converts a hashtable to its code representation. This function is useful for exporting hashtables to .psd1 files. .EXAMPLE $hashtable = @{ Key1 = 'Value1' Key2 = @{ NestedKey1 = 'NestedValue1' NestedKey2 = 'NestedValue2' } Key3 = @(1, 2, 3) Key4 = $true } Convert-HashtableToString -Hashtable $hashtable This will return the following string: @{ Key1 = 'Value1' Key2 = @{ NestedKey1 = 'NestedValue1' NestedKey2 = 'NestedValue2' } Key3 = @(1, 2, 3) Key4 = $true } .NOTES General notes #> [CmdletBinding()] param ( # The hashtable to convert to a string. [Parameter(Mandatory)] [object]$Hashtable, # The indentation level. [Parameter()] [int]$IndentLevel = 0 ) $lines = @() $lines += '@{' $indent = ' ' * $IndentLevel foreach ($key in $Hashtable.Keys) { Write-Verbose "Processing key: $key" $value = $Hashtable[$key] Write-Verbose "Processing value: $value" if ($null -eq $value) { Write-Verbose "Value type: `$null" continue } Write-Verbose "Value type: $($value.GetType().Name)" if (($value -is [System.Collections.Hashtable]) -or ($value -is [System.Collections.Specialized.OrderedDictionary])) { $nestedString = Convert-HashtableToString -Hashtable $value -IndentLevel ($IndentLevel + 1) $lines += "$indent $key = $nestedString" } elseif ($value -is [System.Management.Automation.PSCustomObject]) { $nestedString = Convert-HashtableToString -Hashtable $value -IndentLevel ($IndentLevel + 1) $lines += "$indent $key = $nestedString" } elseif ($value -is [System.Management.Automation.PSObject]) { $nestedString = Convert-HashtableToString -Hashtable $value -IndentLevel ($IndentLevel + 1) $lines += "$indent $key = $nestedString" } elseif ($value -is [bool]) { $lines += "$indent $key = `$$($value.ToString().ToLower())" } elseif ($value -is [int]) { $lines += "$indent $key = $value" } elseif ($value -is [array]) { if ($value.Count -eq 0) { $lines += "$indent $key = @()" } else { $lines += "$indent $key = @(" $value | ForEach-Object { $nestedValue = $_ Write-Verbose "Processing array element: $_" Write-Verbose "Element type: $($_.GetType().Name)" if (($nestedValue -is [System.Collections.Hashtable]) -or ($nestedValue -is [System.Collections.Specialized.OrderedDictionary])) { $nestedString = Convert-HashtableToString -Hashtable $nestedValue -IndentLevel ($IndentLevel + 1) $lines += "$indent $nestedString" } elseif ($nestedValue -is [bool]) { $lines += "$indent `$$($nestedValue.ToString().ToLower())" } elseif ($nestedValue -is [int]) { $lines += "$indent $nestedValue" } else { $lines += "$indent '$nestedValue'" } } $lines += "$indent )" } } else { $lines += "$indent $key = '$value'" } } $lines += "$indent}" return $lines -join "`n" } Write-Verbose "[$scriptName] - [public] - [Hashtable] - [Convert-HashtableToString] - Done" #endregion - From [public] - [Hashtable] - [Convert-HashtableToString] #region - From [public] - [Hashtable] - [Merge-Hashtable] Write-Verbose "[$scriptName] - [public] - [Hashtable] - [Merge-Hashtable] - Importing" function Merge-Hashtable { <# .SYNOPSIS Merge two hashtables, with the second hashtable overriding the first .DESCRIPTION Merge two hashtables, with the second hashtable overriding the first .EXAMPLE $Main = [ordered]@{ Action = '' Location = 'Main' Name = 'Main' Mode = 'Main' } $Override1 = [ordered]@{ Action = '' Location = '' Name = 'Override1' Mode = 'Override1' } $Override2 = [ordered]@{ Action = '' Location = '' Name = 'Override1' Mode = 'Override2' } Merge-Hashtables -Main $Main -Overrides $Override1, $Override2 #> [OutputType([Hashtable])] [Alias('Merge-Hashtables')] [CmdletBinding()] param ( # Main hashtable [Parameter(Mandatory)] [object] $Main, # Hashtable with overrides. # Providing a list of overrides will apply them in order. # Last write wins. [Parameter(Mandatory)] [object[]] $Overrides ) $Output = $Main.Clone() foreach ($Override in $Overrides) { foreach ($Key in $Override.Keys) { if (($Output.Keys) -notcontains $Key) { $Output.$Key = $Override.$Key } if ($Override.item($Key) | IsNotNullOrEmpty) { $Output.$Key = $Override.$Key } } } return $Output } Write-Verbose "[$scriptName] - [public] - [Hashtable] - [Merge-Hashtable] - Done" #endregion - From [public] - [Hashtable] - [Merge-Hashtable] Write-Verbose "[$scriptName] - [public] - [Hashtable] - Done" #endregion - From [public] - [Hashtable] #region - From [public] - [PowerShell] Write-Verbose "[$scriptName] - [public] - [PowerShell] - Processing folder" #region - From [public] - [PowerShell] - [Module] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - Processing folder" #region - From [public] - [PowerShell] - [Module] - [Add-ModuleManifestData] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Add-ModuleManifestData] - Importing" function Add-ModuleManifestData { <# .SYNOPSIS Add data to a module manifest file property .DESCRIPTION This function adds data to a module manifest file property. If the property doesn't exist, it will be created. If it does exist, the new data will be appended to the existing data. .EXAMPLE Add-ModuleManifestData -Path 'MyModule.psd1' -RequiredModules 'pester', 'platyPS' Adds the modules 'pester' and 'platyPS' to the RequiredModules property of the module manifest file 'MyModule.psd1'. #> [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateScript({ Test-Path -Path $_ -PathType Leaf })] [string]$Path, # Modules that must be imported into the global environment prior to importing this module. [Parameter()] [Object[]] $RequiredModules, # Compatible editions of PowerShell. [Parameter()] [string[]] $CompatiblePSEditions, # Assemblies that must be loaded prior to importing this module. [Parameter()] [string[]] $RequiredAssemblies, # Script files (.ps1) that are run in the caller's environment prior to importing this module. [Parameter()] [string[]] $ScriptsToProcess, # Type files (.ps1xml) to be loaded when importing this module. [Parameter()] [string[]] $TypesToProcess, # Format files (.ps1xml) to be loaded when importing this module. [Parameter()] [string[]] $FormatsToProcess, # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess. [Parameter()] [Object[]] $NestedModules, # Functions to export from this module, for best performance, do not use wildcards and do not # delete the entry, use an empty array if there are no functions to export. [Parameter()] [string[]] $FunctionsToExport, # Cmdlets to export from this module, for best performance, do not use wildcards and do not # delete the entry, use an empty array if there are no cmdlets to export. [Parameter()] [string[]] $CmdletsToExport, # Variables to export from this module. [Parameter()] [string[]] $VariablesToExport, # Aliases to export from this module, for best performance, do not use wildcards and do not # delete the entry, use an empty array if there are no aliases to export. [Parameter()] [string[]] $AliasesToExport, # DSC resources to export from this module. [Parameter()] [string[]] $DscResourcesToExport, # List of all modules packaged with this module. [Parameter()] [Object[]] $ModuleList, # List of all files packaged with this module. [Parameter()] [string[]] $FileList, # Tags applied to this module. These help with module discovery in online galleries. [Parameter()] [string[]] $Tags, # External dependent modules of this module. [Parameter()] [string[]] $ExternalModuleDependencies ) $moduleManifest = Get-ModuleManifest -Path $Path $changes = @{} if ($RequiredModules) { $RequiredModules += $moduleManifest.RequiredModules $changes.RequiredModules = $RequiredModules } if ($RequiredAssemblies) { $RequiredAssemblies += $moduleManifest.RequiredAssemblies $changes.RequiredAssemblies = $RequiredAssemblies } if ($CompatiblePSEditions) { $CompatiblePSEditions += $moduleManifest.CompatiblePSEditions $changes.CompatiblePSEditions = $CompatiblePSEditions } if ($ScriptsToProcess) { $ScriptsToProcess += $moduleManifest.ScriptsToProcess $changes.ScriptsToProcess = $ScriptsToProcess } if ($TypesToProcess) { $TypesToProcess += $moduleManifest.TypesToProcess $changes.TypesToProcess = $TypesToProcess } if ($FormatsToProcess) { $FormatsToProcess += $moduleManifest.FormatsToProcess $changes.FormatsToProcess = $FormatsToProcess } if ($NestedModules) { $NestedModules += $moduleManifest.NestedModules $changes.NestedModules = $NestedModules } if ($FunctionsToExport) { $FunctionsToExport += $moduleManifest.FunctionsToExport $changes.FunctionsToExport = $FunctionsToExport } if ($CmdletsToExport) { $CmdletsToExport += $moduleManifest.CmdletsToExport $changes.CmdletsToExport = $CmdletsToExport } if ($VariablesToExport) { $VariablesToExport += $moduleManifest.VariablesToExport $changes.VariablesToExport = $VariablesToExport } if ($AliasesToExport) { $AliasesToExport += $moduleManifest.AliasesToExport $changes.AliasesToExport = $AliasesToExport } if ($DscResourcesToExport) { $DscResourcesToExport += $moduleManifest.DscResourcesToExport $changes.DscResourcesToExport = $DscResourcesToExport } if ($ModuleList) { $ModuleList += $moduleManifest.ModuleList $changes.ModuleList = $ModuleList } if ($FileList) { $FileList += $moduleManifest.FileList $changes.FileList = $FileList } if ($Tags) { $Tags += $moduleManifest.PrivateData.PSData.Tags $changes.Tags = $Tags } if ($ExternalModuleDependencies) { $ExternalModuleDependencies += $moduleManifest.PrivateData.PSData.ExternalModuleDependencies $changes.ExternalModuleDependencies = $ExternalModuleDependencies } foreach ($key in $changes.GetEnumerator().Name) { $changes[$key] = $changes[$key] | Sort-Object -Unique | Where-Object { $_ | IsNotNullOrEmpty } } Set-ModuleManifest -Path $Path @changes } Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Add-ModuleManifestData] - Done" #endregion - From [public] - [PowerShell] - [Module] - [Add-ModuleManifestData] #region - From [public] - [PowerShell] - [Module] - [Add-PSModulePath] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Add-PSModulePath] - Importing" function Add-PSModulePath { <# .SYNOPSIS Adds a path to the PSModulePath environment variable. .DESCRIPTION Adds a path to the PSModulePath environment variable. For Linux and macOS, the path delimiter is ':' and for Windows it is ';'. .EXAMPLE Add-PSModulePath -Path 'C:\Users\user\Documents\WindowsPowerShell\Modules' Adds the path 'C:\Users\user\Documents\WindowsPowerShell\Modules' to the PSModulePath environment variable. #> [CmdletBinding()] param( # Path to the folder where the module source code is located. [Parameter(Mandatory)] [string] $Path ) $PSModulePathSeparator = [System.IO.Path]::PathSeparator $env:PSModulePath += "$PSModulePathSeparator$Path" Write-Verbose 'PSModulePath:' $env:PSModulePath.Split($PSModulePathSeparator) | ForEach-Object { Write-Verbose " - [$_]" } } Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Add-PSModulePath] - Done" #endregion - From [public] - [PowerShell] - [Module] - [Add-PSModulePath] #region - From [public] - [PowerShell] - [Module] - [Export-PowerShellDataFile] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Export-PowerShellDataFile] - Importing" function Export-PowerShellDataFile { <# .SYNOPSIS Export a hashtable to a .psd1 file. .DESCRIPTION This function exports a hashtable to a .psd1 file. It also formats the .psd1 file using the Format-ModuleManifest cmdlet. .EXAMPLE Export-PowerShellDataFile -Hashtable @{ Name = 'MyModule'; ModuleVersion = '1.0.0' } -Path 'MyModule.psd1' #> [CmdletBinding()] param ( # The hashtable to export to a .psd1 file. [Parameter(Mandatory)] [object] $Hashtable, # The path of the .psd1 file to export. [Parameter(Mandatory)] [string] $Path, # Force the export, even if the file already exists. [Parameter()] [switch] $Force ) $content = Convert-HashtableToString -Hashtable $Hashtable $content | Out-File -FilePath $Path -Force:$Force Format-ModuleManifest -Path $Path } Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Export-PowerShellDataFile] - Done" #endregion - From [public] - [PowerShell] - [Module] - [Export-PowerShellDataFile] #region - From [public] - [PowerShell] - [Module] - [Format-ModuleManifest] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Format-ModuleManifest] - Importing" function Format-ModuleManifest { <# .SYNOPSIS Formats a module manifest file. .DESCRIPTION This function formats a module manifest file, by removing comments and empty lines, and then formatting the file using the `Invoke-Formatter` function. .EXAMPLE Format-ModuleManifest -Path 'C:\MyModule\MyModule.psd1' #> [CmdletBinding()] param( # Path to the module manifest file. [Parameter(Mandatory)] [string] $Path ) $Utf8BomEncoding = New-Object System.Text.UTF8Encoding $true $manifestContent = Get-Content -Path $Path $manifestContent = $manifestContent | ForEach-Object { $_ -replace '#.*' } $manifestContent = $manifestContent | ForEach-Object { $_.TrimEnd() } $manifestContent = $manifestContent | Where-Object { $_ | IsNotNullOrEmpty } [System.IO.File]::WriteAllLines($Path, $manifestContent, $Utf8BomEncoding) $manifestContent = Get-Content -Path $Path -Raw $content = Invoke-Formatter -ScriptDefinition $manifestContent [System.IO.File]::WriteAllLines($Path, $content, $Utf8BomEncoding) } Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Format-ModuleManifest] - Done" #endregion - From [public] - [PowerShell] - [Module] - [Format-ModuleManifest] #region - From [public] - [PowerShell] - [Module] - [Get-ModuleManifest] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Get-ModuleManifest] - Importing" function Get-ModuleManifest { <# .SYNOPSIS Get the module manifest. .DESCRIPTION Get the module manifest as a path, file info, content, or hashtable. .EXAMPLE Get-PSModuleManifest -Path 'src/PSModule/PSModule.psd1' -As Hashtable #> [OutputType([string], [System.IO.FileInfo], [System.Collections.Hashtable], [System.Collections.Specialized.OrderedDictionary])] [CmdletBinding()] param( # Path to the module manifest file. [Parameter(Mandatory)] [string] $Path, # The format of the output. [Parameter()] [ValidateSet('FileInfo', 'Content', 'Hashtable')] [string] $As = 'Hashtable' ) if (-not (Test-Path -Path $Path)) { Write-Warning 'No manifest file found.' return $null } Write-Verbose "Found manifest file [$Path]" switch ($As) { 'FileInfo' { return Get-Item -Path $Path } 'Content' { return Get-Content -Path $Path } 'Hashtable' { $manifest = [System.Collections.Specialized.OrderedDictionary]@{} $psData = [System.Collections.Specialized.OrderedDictionary]@{} $privateData = [System.Collections.Specialized.OrderedDictionary]@{} $tempManifest = Import-PowerShellDataFile -Path $Path if ($tempManifest.ContainsKey('PrivateData')) { $tempPrivateData = $tempManifest.PrivateData if ($tempPrivateData.ContainsKey('PSData')) { $tempPSData = $tempPrivateData.PSData $tempPrivateData.Remove('PSData') } } $psdataOrder = @( 'Tags' 'LicenseUri' 'ProjectUri' 'IconUri' 'ReleaseNotes' 'Prerelease' 'RequireLicenseAcceptance' 'ExternalModuleDependencies' ) foreach ($key in $psdataOrder) { if (($null -ne $tempPSData) -and ($tempPSData.ContainsKey($key))) { $psData.$key = $tempPSData.$key } } if ($psData.Count -gt 0) { $privateData.PSData = $psData } else { $privateData.Remove('PSData') } foreach ($key in $tempPrivateData.Keys) { $privateData.$key = $tempPrivateData.$key } $manifestOrder = @( 'RootModule' 'ModuleVersion' 'CompatiblePSEditions' 'GUID' 'Author' 'CompanyName' 'Copyright' 'Description' 'PowerShellVersion' 'PowerShellHostName' 'PowerShellHostVersion' 'DotNetFrameworkVersion' 'ClrVersion' 'ProcessorArchitecture' 'RequiredModules' 'RequiredAssemblies' 'ScriptsToProcess' 'TypesToProcess' 'FormatsToProcess' 'NestedModules' 'FunctionsToExport' 'CmdletsToExport' 'VariablesToExport' 'AliasesToExport' 'DscResourcesToExport' 'ModuleList' 'FileList' 'HelpInfoURI' 'DefaultCommandPrefix' 'PrivateData' ) foreach ($key in $manifestOrder) { if ($tempManifest.ContainsKey($key)) { $manifest.$key = $tempManifest.$key } } if ($privateData.Count -gt 0) { $manifest.PrivateData = $privateData } else { $manifest.Remove('PrivateData') } return $manifest } } } Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Get-ModuleManifest] - Done" #endregion - From [public] - [PowerShell] - [Module] - [Get-ModuleManifest] #region - From [public] - [PowerShell] - [Module] - [Invoke-PruneModule] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Invoke-PruneModule] - Importing" function Invoke-PruneModule { <# .SYNOPSIS Remove all but the newest version of a module .DESCRIPTION Remove all but the newest version of a module .EXAMPLE Invoke-PruneModule -Name 'Az.*' -Scope CurrentUser #> [OutputType([void])] [CmdletBinding()] [Alias('Prune-Module')] param ( # Name of the module(s) to prune [Parameter()] [string[]] $Name = '*', # Scope of the module(s) to prune [Parameter()] [ValidateSet('CurrentUser', 'AllUsers')] [string[]] $Scope = 'CurrentUser' ) if ($Scope -eq 'AllUsers' -and -not (IsAdmin)) { $message = 'Administrator rights are required to uninstall modules for all users. Please run the command again with' + " elevated rights (Run as Administrator) or provide '-Scope CurrentUser' to your command." throw $message } $UpdateableModules = Get-InstalledModule | Where-Object Name -Like "$Name" $UpdateableModuleNames = $UpdateableModules.Name | Sort-Object -Unique foreach ($UpdateableModuleName in $UpdateableModuleNames) { $UpdateableModule = $UpdateableModules | Where-Object Name -EQ $UpdateableModuleName | Sort-Object -Property Version -Descending Write-Verbose "[$($UpdateableModuleName)] - Found [$($UpdateableModule.Count)]" $NewestModule = $UpdateableModule | Select-Object -First 1 Write-Verbose "[$($UpdateableModuleName)] - Newest [$($NewestModule.Version -join ', ')]" $OutdatedModules = $UpdateableModule | Select-Object -Skip 1 Write-Verbose "[$($UpdateableModuleName)] - Outdated [$($OutdatedModules.Version -join ', ')]" foreach ($OutdatedModule in $OutdatedModules) { Write-Verbose "[$($UpdateableModuleName)] - [$($OutdatedModule.Version)] - Removing" $OutdatedModule | Remove-Module -Force Write-Verbose "[$($UpdateableModuleName)] - [$($OutdatedModule.Version)] - Uninstalling" Uninstall-Module -Name $OutdatedModule.Name -RequiredVersion -Force try { $OutdatedModule.ModuleBase | Remove-Item -Force -Recurse -ErrorAction Stop } catch { Write-Warning "[$($UpdateableModuleName)] - [$($OutdatedModule.Version)] - Failed to remove [$($OutdatedModule.ModuleBase)]" continue } } } } Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Invoke-PruneModule] - Done" #endregion - From [public] - [PowerShell] - [Module] - [Invoke-PruneModule] #region - From [public] - [PowerShell] - [Module] - [Invoke-ReinstallModule] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Invoke-ReinstallModule] - Importing" function Invoke-ReinstallModule { <# .SYNOPSIS Reinstalls module into a given scope. .DESCRIPTION Reinstalls module into a given scope. This is useful when you want to reinstall or clean up your module versions. With this command you always get the newest available version of the module and all the previous version wiped out. .PARAMETER Name The name of the module to be reinstalled. Wildcards are supported. .PARAMETER Scope The scope of the module to will be reinstalled to. .EXAMPLE Reinstall-Module -Name Pester -Scope CurrentUser Reinstall Pester module for the current user. .EXAMPLE Reinstall-Module -Scope CurrentUser Reinstall all reinstallable modules into the current user. #> [CmdletBinding()] [Alias('Reinstall-Module')] param ( # Name of the module(s) to reinstall [Parameter()] [SupportsWildcards()] [string[]] $Name = '*', # Scope of the module(s) to reinstall [Parameter()] [ValidateSet('CurrentUser', 'AllUsers')] [string[]] $Scope = 'CurrentUser' ) if ($Scope -eq 'AllUsers' -and -not (IsAdmin)) { $message = 'Administrator rights are required to uninstall modules for all users. Please run the command again with' + " elevated rights (Run as Administrator) or provide '-Scope CurrentUser' to your command." throw $message } $modules = Get-InstalledModule | Where-Object Name -Like "$Name" Write-Verbose "Found [$($modules.Count)] modules" $modules | ForEach-Object { if ($_.name -eq 'Pester') { Uninstall-Pester -All continue } Uninstall-Module -Name $_ -AllVersions -Force -ErrorAction SilentlyContinue } $modules.Name | ForEach-Object { Install-Module -Name $_ -Scope $Scope -Force } } Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Invoke-ReinstallModule] - Done" #endregion - From [public] - [PowerShell] - [Module] - [Invoke-ReinstallModule] #region - From [public] - [PowerShell] - [Module] - [Set-ModuleManifest] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Set-ModuleManifest] - Importing" filter Set-ModuleManifest { <# .SYNOPSIS Sets the values of a module manifest file. .DESCRIPTION This function sets the values of a module manifest file. Very much like the Update-ModuleManifest function, but allows values to be missing. .EXAMPLE Set-ModuleManifest -Path 'C:\MyModule\MyModule.psd1' -ModuleVersion '1.0.0' #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function does not change state.' )] [CmdletBinding()] param( # Path to the module manifest file. [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )] [string] $Path, #Script module or binary module file associated with this manifest. [Parameter()] [AllowNull()] [string] $RootModule, #Version number of this module. [Parameter()] [AllowNull()] [Version] $ModuleVersion, # Supported PSEditions. [Parameter()] [AllowNull()] [string[]] $CompatiblePSEditions, # ID used to uniquely identify this module. [Parameter()] [AllowNull()] [guid] $GUID, # Author of this module. [Parameter()] [AllowNull()] [string] $Author, # Company or vendor of this module. [Parameter()] [AllowNull()] [string] $CompanyName, # Copyright statement for this module. [Parameter()] [AllowNull()] [string] $Copyright, # Description of the functionality provided by this module. [Parameter()] [AllowNull()] [string] $Description, # Minimum version of the PowerShell engine required by this module. [Parameter()] [AllowNull()] [Version] $PowerShellVersion, # Name of the PowerShell host required by this module. [Parameter()] [AllowNull()] [string] $PowerShellHostName, # Minimum version of the PowerShell host required by this module. [Parameter()] [AllowNull()] [version] $PowerShellHostVersion, # Minimum version of Microsoft .NET Framework required by this module. # This prerequisite is valid for the PowerShell Desktop edition only. [Parameter()] [AllowNull()] [Version] $DotNetFrameworkVersion, # Minimum version of the common language runtime (CLR) required by this module. # This prerequisite is valid for the PowerShell Desktop edition only. [Parameter()] [AllowNull()] [Version] $ClrVersion, # Processor architecture (None,X86, Amd64) required by this module [Parameter()] [AllowNull()] [System.Reflection.ProcessorArchitecture] $ProcessorArchitecture, # Modules that must be imported into the global environment prior to importing this module. [Parameter()] [AllowNull()] [Object[]] $RequiredModules, # Assemblies that must be loaded prior to importing this module. [Parameter()] [AllowNull()] [string[]] $RequiredAssemblies, # Script files (.ps1) that are run in the caller's environment prior to importing this module. [Parameter()] [AllowNull()] [string[]] $ScriptsToProcess, # Type files (.ps1xml) to be loaded when importing this module. [Parameter()] [AllowNull()] [string[]] $TypesToProcess, # Format files (.ps1xml) to be loaded when importing this module. [Parameter()] [AllowNull()] [string[]] $FormatsToProcess, # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess. [Parameter()] [AllowNull()] [Object[]] $NestedModules, # Functions to export from this module, for best performance, do not use wildcards and do not # delete the entry, use an empty array if there are no functions to export. [Parameter()] [AllowNull()] [string[]] $FunctionsToExport, # Cmdlets to export from this module, for best performance, do not use wildcards and do not # delete the entry, use an empty array if there are no cmdlets to export. [Parameter()] [AllowNull()] [string[]] $CmdletsToExport, # Variables to export from this module. [Parameter()] [AllowNull()] [string[]] $VariablesToExport, # Aliases to export from this module, for best performance, do not use wildcards and do not # delete the entry, use an empty array if there are no aliases to export. [Parameter()] [AllowNull()] [string[]] $AliasesToExport, # DSC resources to export from this module. [Parameter()] [AllowNull()] [string[]] $DscResourcesToExport, # List of all modules packaged with this module. [Parameter()] [AllowNull()] [Object[]] $ModuleList, # List of all files packaged with this module. [Parameter()] [AllowNull()] [string[]] $FileList, # Tags applied to this module. These help with module discovery in online galleries. [Parameter()] [AllowNull()] [string[]] $Tags, # A URL to the license for this module. [Parameter()] [AllowNull()] [uri] $LicenseUri, # A URL to the main site for this project. [Parameter()] [AllowNull()] [uri] $ProjectUri, # A URL to an icon representing this module. [Parameter()] [AllowNull()] [uri] $IconUri, # ReleaseNotes of this module. [Parameter()] [AllowNull()] [string] $ReleaseNotes, # Prerelease string of this module. [Parameter()] [AllowNull()] [string] $Prerelease, # Flag to indicate whether the module requires explicit user acceptance for install/update/save. [Parameter()] [AllowNull()] [bool] $RequireLicenseAcceptance, # External dependent modules of this module. [Parameter()] [AllowNull()] [string[]] $ExternalModuleDependencies, # HelpInfo URI of this module. [Parameter()] [AllowNull()] [String] $HelpInfoURI, # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. [Parameter()] [AllowNull()] [string] $DefaultCommandPrefix, # Private data to pass to the module specified in RootModule/ModuleToProcess. # This may also contain a PSData hashtable with additional module metadata used by PowerShell. [Parameter()] [AllowNull()] [object] $PrivateData ) $outManifest = [ordered]@{} $outPSData = [ordered]@{} $outPrivateData = [ordered]@{} $tempManifest = Get-ModuleManifest -Path $Path if ($tempManifest.Keys.Contains('PrivateData')) { $tempPrivateData = $tempManifest.PrivateData if ($tempPrivateData.Keys.Contains('PSData')) { $tempPSData = $tempPrivateData.PSData $tempPrivateData.Remove('PSData') } } $psdataOrder = @( 'Tags' 'LicenseUri' 'ProjectUri' 'IconUri' 'ReleaseNotes' 'Prerelease' 'RequireLicenseAcceptance' 'ExternalModuleDependencies' ) foreach ($key in $psdataOrder) { if (($null -ne $tempPSData) -and $tempPSData.Keys.Contains($key)) { $outPSData[$key] = $tempPSData[$key] } if ($PSBoundParameters.Keys.Contains($key)) { if ($null -eq $PSBoundParameters[$key]) { $outPSData.Remove($key) } else { $outPSData[$key] = $PSBoundParameters[$key] } } } if ($outPSData.Count -gt 0) { $outPrivateData.PSData = $outPSData } else { $outPrivateData.Remove('PSData') } foreach ($key in $tempPrivateData.Keys) { $outPrivateData[$key] = $tempPrivateData[$key] } foreach ($key in $PrivateData.Keys) { $outPrivateData[$key] = $PrivateData[$key] } $manifestOrder = @( 'RootModule' 'ModuleVersion' 'CompatiblePSEditions' 'GUID' 'Author' 'CompanyName' 'Copyright' 'Description' 'PowerShellVersion' 'PowerShellHostName' 'PowerShellHostVersion' 'DotNetFrameworkVersion' 'ClrVersion' 'ProcessorArchitecture' 'RequiredModules' 'RequiredAssemblies' 'ScriptsToProcess' 'TypesToProcess' 'FormatsToProcess' 'NestedModules' 'FunctionsToExport' 'CmdletsToExport' 'VariablesToExport' 'AliasesToExport' 'DscResourcesToExport' 'ModuleList' 'FileList' 'HelpInfoURI' 'DefaultCommandPrefix' 'PrivateData' ) foreach ($key in $manifestOrder) { if ($tempManifest.Keys.Contains($key)) { $outManifest[$key] = $tempManifest[$key] } if ($PSBoundParameters.Keys.Contains($key)) { if ($null -eq $PSBoundParameters[$key]) { $outManifest.Remove($key) } else { $outManifest[$key] = $PSBoundParameters[$key] } } } if ($outPrivateData.Count -gt 0) { $outManifest['PrivateData'] = $outPrivateData } else { $outManifest.Remove('PrivateData') } Remove-Item -Path $Path -Force Export-PowerShellDataFile -Hashtable $outManifest -Path $Path } Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Set-ModuleManifest] - Done" #endregion - From [public] - [PowerShell] - [Module] - [Set-ModuleManifest] #region - From [public] - [PowerShell] - [Module] - [Uninstall-Pester] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Uninstall-Pester] - Importing" function Uninstall-Pester { <# .SYNOPSIS Uninstall Pester 3 from Program Files and Program Files (x86) .DESCRIPTION Uninstall Pester 3 from Program Files and Program Files (x86). This is useful when you want to install Pester 4 and you have Pester 3 installed. .PARAMETER All .EXAMPLE Uninstall-Pester Uninstall Pester 3 from Program Files and Program Files (x86). .EXAMPLE Uninstall-Pester -All Completely remove all built-in Pester 3 installations. #> [OutputType([String])] [CmdletBinding()] param ( # Completely remove all built-in Pester 3 installations [Parameter()] [switch] $All ) $pesterPaths = foreach ($programFiles in ($env:ProgramFiles, ${env:ProgramFiles(x86)})) { $path = "$programFiles\WindowsPowerShell\Modules\Pester" if ($null -ne $programFiles -and (Test-Path $path)) { if ($All) { Get-Item $path } else { Get-ChildItem "$path\3.*" } } } if (-not $pesterPaths) { "There are no Pester$(if (-not $all) {' 3'}) installations in Program Files and Program Files (x86) doing nothing." return } foreach ($pesterPath in $pesterPaths) { takeown /F $pesterPath /A /R icacls $pesterPath /reset # grant permissions to Administrators group, but use SID to do # it because it is localized on non-us installations of Windows icacls $pesterPath /grant '*S-1-5-32-544:F' /inheritance:d /T Remove-Item -Path $pesterPath -Recurse -Force -Confirm:$false } } Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - [Uninstall-Pester] - Done" #endregion - From [public] - [PowerShell] - [Module] - [Uninstall-Pester] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Module] - Done" #endregion - From [public] - [PowerShell] - [Module] #region - From [public] - [PowerShell] - [Object] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Object] - Processing folder" #region - From [public] - [PowerShell] - [Object] - [Copy-Object] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Object] - [Copy-Object] - Importing" filter Copy-Object { <# .SYNOPSIS Copy an object .DESCRIPTION Copy an object .EXAMPLE $Object | Copy-Object Copy an object #> [OutputType([object])] [CmdletBinding()] param ( # Object to copy [Parameter( Mandatory, ValueFromPipeline )] [Object] $InputObject ) $InputObject | ConvertTo-Json -Depth 100 | ConvertFrom-Json } Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Object] - [Copy-Object] - Done" #endregion - From [public] - [PowerShell] - [Object] - [Copy-Object] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [Object] - Done" #endregion - From [public] - [PowerShell] - [Object] #region - From [public] - [PowerShell] - [PSCredential] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [PSCredential] - Processing folder" #region - From [public] - [PowerShell] - [PSCredential] - [New-PSCredential] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [PSCredential] - [New-PSCredential] - Importing" function New-PSCredential { <# .SYNOPSIS Creates a PSCredential .DESCRIPTION Takes in a UserName and a plain text password and creates a PSCredential .EXAMPLE New-PSCredential -UserName "Admin" -Password "P@ssw0rd!" This creates a PSCredential with username "Admin" and password "P@ssw0rd!" .EXAMPLE New-PSCredential -UserName "Admin" Prompts user for password and creates a PSCredential with username "Admin" and password the user provided. .EXAMPLE $SecretPassword = "P@ssw0rd!" | ConvertTo-SecureString -Force New-PSCredential -UserName "Admin" -Password $SecretPassword #> [OutputType([System.Management.Automation.PSCredential])] [Cmdletbinding(SupportsShouldProcess)] param( # The username of the PSCredential [Parameter()] [string] $Username = (Read-Host -Prompt 'Enter a username'), # The plain text password of the PSCredential [Parameter()] [SecureString] $Password = (Read-Host -Prompt 'Enter Password' -AsSecureString) ) if ($PSCmdlet.ShouldProcess('PSCredential', 'Create a new')) { New-Object -TypeName System.Management.Automation.PSCredential($Username, $Password) } } Write-Verbose "[$scriptName] - [public] - [PowerShell] - [PSCredential] - [New-PSCredential] - Done" #endregion - From [public] - [PowerShell] - [PSCredential] - [New-PSCredential] #region - From [public] - [PowerShell] - [PSCredential] - [Restore-PSCredential] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [PSCredential] - [Restore-PSCredential] - Importing" function Restore-PSCredential { <# .SYNOPSIS Restores a PSCredential from a file. .DESCRIPTION Takes in a UserName and restores a PSCredential from a file. .EXAMPLE Restore-PSCredential -UserName "Admin" This restores the PSCredential from the default location of $env:HOMEPATH\.creds\Admin.cred .EXAMPLE Restore-PSCredential -UserName "Admin" -Path "C:\Temp" This restores the PSCredential from the location of C:\Temp\Admin.cred #> [OutputType([System.Management.Automation.PSCredential])] [CmdletBinding()] param( # The username of the PSCredential [Parameter(Mandatory)] [string] $UserName, # The folder path to restore the PSCredential from. [Parameter()] [string] $Path = "$env:HOMEPATH\.creds" ) $fileName = "$UserName.cred" $credFilePath = Join-Path -Path $Path -ChildPath $fileName $credFilePathExists = Test-Path $credFilePath if ($credFilePathExists) { $secureString = Get-Content $credFilePath | ConvertTo-SecureString $credential = New-Object -TypeName System.Management.Automation.PSCredential($UserName, $secureString) } else { throw "Unable to locate a credential file for $($Username)" } return $credential } Write-Verbose "[$scriptName] - [public] - [PowerShell] - [PSCredential] - [Restore-PSCredential] - Done" #endregion - From [public] - [PowerShell] - [PSCredential] - [Restore-PSCredential] #region - From [public] - [PowerShell] - [PSCredential] - [Save-PSCredential] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [PSCredential] - [Save-PSCredential] - Importing" filter Save-PSCredential { <# .SYNOPSIS Saves a PSCredential to a file. .DESCRIPTION Takes in a PSCredential and saves it to a file. .EXAMPLE $Credential = New-PSCredential -UserName "Admin" -Password "P@ssw0rd!" Save-PSCredential -Credential $Credential This saves the PSCredential to the default location of $env:HOMEPATH\.creds\Admin.cred .EXAMPLE $Credential = New-PSCredential -UserName "Admin" -Password "P@ssw0rd!" Save-PSCredential -Credential $Credential -Path "C:\Temp" This saves the PSCredential to the location of C:\Temp\Admin.cred #> [OutputType([void])] [CmdletBinding()] param( # The PSCredential to save. [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )] [System.Management.Automation.PSCredential] $Credential, # The folder path to save the PSCredential to. [Parameter()] [string] $Path = "$env:HOMEPATH\.creds" ) $fileName = "$($Credential.UserName).cred" $credFilePath = Join-Path -Path $Path -ChildPath $fileName $credFilePathExists = Test-Path $credFilePath if (-not $credFilePathExists) { try { $null = New-Item -ItemType File -Path $credFilePath -ErrorAction Stop -Force } catch { throw $_.Exception.Message } } $Credential.Password | ConvertFrom-SecureString | Out-File $credFilePath -Force } # $SecurePassword = ConvertTo-SecureString $PlainPassword -AsPlainText -Force # $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword) # $UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) # [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR) Write-Verbose "[$scriptName] - [public] - [PowerShell] - [PSCredential] - [Save-PSCredential] - Done" #endregion - From [public] - [PowerShell] - [PSCredential] - [Save-PSCredential] Write-Verbose "[$scriptName] - [public] - [PowerShell] - [PSCredential] - Done" #endregion - From [public] - [PowerShell] - [PSCredential] Write-Verbose "[$scriptName] - [public] - [PowerShell] - Done" #endregion - From [public] - [PowerShell] #region - From [public] - [String] Write-Verbose "[$scriptName] - [public] - [String] - Processing folder" #region - From [public] - [String] - [Casing] Write-Verbose "[$scriptName] - [public] - [String] - [Casing] - Processing folder" #region - From [public] - [String] - [Casing] - [Convert-StringCasingStyle] Write-Verbose "[$scriptName] - [public] - [String] - [Casing] - [Convert-StringCasingStyle] - Importing" filter Convert-StringCasingStyle { <# .SYNOPSIS Convert a string to a different casing style .DESCRIPTION This function converts a string to a different casing style. .EXAMPLE 'thisIsCamelCase' | Convert-StringCasingStyle -To 'snake_case' Convert the string 'thisIsCamelCase' to 'this_is_camel_case' .EXAMPLE 'thisIsCamelCase' | Convert-StringCasingStyle -To 'UPPER_SNAKE_CASE' Convert the string 'thisIsCamelCase' to 'THIS_IS_CAMEL_CASE' .EXAMPLE 'thisIsCamelCase' | Convert-StringCasingStyle -To 'kebab-case' .NOTES General notes #> [OutputType([string])] [CmdletBinding()] param ( # The string to convert [Parameter( Mandatory, ValueFromPipeline )] [string] $Text, # The casing style to convert the string to [Parameter(Mandatory)] [ValidateSet( 'lowercase', 'UPPERCASE', 'Title Case', 'Sentencecase', 'PascalCase', 'camelCase', 'kebab-case', 'UPPER-KEBAB-CASE', 'snake_case', 'UPPER_SNAKE_CASE' )] [string] $To ) $currentStyle = Get-StringCasingStyle -Text $Text $words = Split-StringByCasingStyle -Text $Text -By $currentStyle # Convert the words into the target style switch ($To) { 'lowercase' { ($words -join '').toLower() } 'UPPERCASE' { ($words -join '').toUpper() } 'Title Case' { ($words | ForEach-Object { $_.Substring(0, 1).ToUpper() + $_.Substring(1).ToLower() }) -join ' ' } 'Sentencecase' { $words -join '' | ForEach-Object { $_.Substring(0, 1).ToUpper() + $_.Substring(1).ToLower() } } 'kebab-case' { ($words -join '-').ToLower() } 'snake_case' { ($words -join '_').ToLower() } 'PascalCase' { ($words | ForEach-Object { $_.Substring(0, 1).ToUpper() + $_.Substring(1).ToLower() }) -join '' } 'camelCase' { $words[0].toLower() + (($words | Select-Object -Skip 1 | ForEach-Object { $_.Substring(0, 1).ToUpper() + $_.Substring(1) }) -join '') } 'UPPER_SNAKE_CASE' { ($words -join '_').toUpper() } 'UPPER-KEBAB-CASE' { ($words -join '-').toUpper() } } } Write-Verbose "[$scriptName] - [public] - [String] - [Casing] - [Convert-StringCasingStyle] - Done" #endregion - From [public] - [String] - [Casing] - [Convert-StringCasingStyle] #region - From [public] - [String] - [Casing] - [Get-StringCasingStyle] Write-Verbose "[$scriptName] - [public] - [String] - [Casing] - [Get-StringCasingStyle] - Importing" filter Get-StringCasingStyle { <# .SYNOPSIS Detects the casing style of a string .DESCRIPTION This function detects the casing style of a string. .EXAMPLE 'testtesttest' | Get-StringCasingStyle lowercase .EXAMPLE 'TESTTESTTEST' | Get-StringCasingStyle UPPERCASE .EXAMPLE 'Testtesttest' | Get-StringCasingStyle Sentencecase .EXAMPLE 'TestTestTest' | Get-StringCasingStyle PascalCase .EXAMPLE 'testTestTest' | Get-StringCasingStyle camelCase .EXAMPLE 'test-test-test' | Get-StringCasingStyle kebab-case .EXAMPLE 'TEST-TEST-TEST' | Get-StringCasingStyle UPPER-KEBAB-CASE .EXAMPLE 'test_test_test' | Get-StringCasingStyle snake_case .EXAMPLE 'TEST_TEST_TEST' | Get-StringCasingStyle UPPER_SNAKE_CASE .EXAMPLE 'Test_teSt-Test' | Get-StringCasingStyle Unknown #> [OutputType([string])] [CmdletBinding()] param ( # The string to check the casing style of [Parameter( Mandatory, ValueFromPipeline )] [ValidateNotNullOrEmpty()] [string] $Text ) $style = if ([regex]::Match($Text, '^[a-z][a-z0-9]*$').Success) { 'lowercase' } elseif ([regex]::Match($Text, '^[A-Z][A-Z0-9]*$').Success) { 'UPPERCASE' } elseif ([regex]::Match($Text, '^[A-Z][a-z0-9]*$').Success) { 'Sentencecase' } elseif ([regex]::Match($Text, '^([A-Z][a-z]*)(\s+[A-Z][a-z]*)+$').Success) { 'Title Case' } elseif ([regex]::Match($Text, '^[A-Z][a-z0-9]*([A-Z][a-z0-9]*)+$').Success) { 'PascalCase' } elseif ([regex]::Match($Text, '^[a-z][a-z0-9]*([A-Z][a-z0-9]*)+$').Success) { 'camelCase' } elseif ([regex]::Match($Text, '^[a-z][a-z0-9]*(-[a-z0-9]+)+$').Success) { 'kebab-case' } elseif ([regex]::Match($Text, '^[A-Z][A-Z0-9]*(-[A-Z0-9]+)+$').Success) { 'UPPER-KEBAB-CASE' } elseif ([regex]::Match($Text, '^[a-z][a-z0-9]*(_[a-z0-9]+)+$').Success) { 'snake_case' } elseif ([regex]::Match($Text, '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$').Success) { 'UPPER_SNAKE_CASE' } else { 'Unknown' } Write-Verbose "Detected casing style: [$style]" $style } Write-Verbose "[$scriptName] - [public] - [String] - [Casing] - [Get-StringCasingStyle] - Done" #endregion - From [public] - [String] - [Casing] - [Get-StringCasingStyle] #region - From [public] - [String] - [Casing] - [Split-StringByCasingStyle] Write-Verbose "[$scriptName] - [public] - [String] - [Casing] - [Split-StringByCasingStyle] - Importing" filter Split-StringByCasingStyle { <# .SYNOPSIS Splits a kebab-case string into an array of words .DESCRIPTION This function splits a kebab-case string into an array of words. .EXAMPLE Split-StringByCasingStyle -Text 'this-is-a-kebab-case-string' -By kebab-case this is a kebab case string .EXAMPLE Split-StringByCasingStyle -Text 'this_is_a_kebab_case_string' -By 'snake_case' this is a kebab case string .EXAMPLE Split-StringByCasingStyle -Text 'ThisIsAPascalCaseString' -By 'PascalCase' This Is A Pascal Case String .EXAMPLE Split-StringByCasingStyle -Text 'thisIsACamelCaseString' -By 'camelCase' this Is A Camel Case String .EXAMPLE Split-StringByCasingStyle -Text 'this_is_a-CamelCaseString' -By kebab-case | Split-StringByCasingStyle -By snake_case this_is_a camelcasestring #> [OutputType([string[]])] [CmdletBinding()] param ( # The string to split [Parameter( Mandatory, ValueFromPipeline )] [string] $Text, # The casing style to split the string by [Parameter()] [ValidateSet( 'lowercase', 'UPPERCASE', 'Sentencecase', 'Title Case', 'PascalCase', 'camelCase', 'kebab-case', 'UPPER-KEBAB-CASE', 'snake_case', 'UPPER_SNAKE_CASE' )] [string] $By ) $styles = $PSBoundParameters | Where-Object { $_.Value -eq $true } | Select-Object -ExpandProperty Name Write-Verbose "Splitting string [$Text] by casing style [$($styles -join ', ' )]" $splitText = switch ($By) { 'PascalCase' { [regex]::Matches($Text, '([A-Z][a-z]*)').Value; break } 'camelCase' { [regex]::Matches($Text, '([A-Z][a-z]*)|^[a-z]+').Value; break } 'kebab-case' { $Text -split '-'; break } 'UPPER-KEBAB-CASE' { $Text -split '-'; break } 'snake_case' { $Text -split '_'; break } 'UPPER_SNAKE_CASE' { $Text -split '_'; break } default { $Text -split ' ' } } Write-Verbose "Result: [$($splitText -join ', ')]" $splitText } Write-Verbose "[$scriptName] - [public] - [String] - [Casing] - [Split-StringByCasingStyle] - Done" #endregion - From [public] - [String] - [Casing] - [Split-StringByCasingStyle] Write-Verbose "[$scriptName] - [public] - [String] - [Casing] - Done" #endregion - From [public] - [String] - [Casing] #region - From [public] - [String] - [Test-IsNotNullOrEmpty] Write-Verbose "[$scriptName] - [public] - [String] - [Test-IsNotNullOrEmpty] - Importing" filter Test-IsNotNullOrEmpty { <# .SYNOPSIS Test if an object is not null or empty .DESCRIPTION Test if an object is not null or empty .EXAMPLE '' | Test-IsNotNullOrEmpty False #> [OutputType([bool])] [Cmdletbinding()] [Alias('IsNotNullOrEmpty')] param( # Object to test [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )] [AllowNull()] [object] $Object ) return -not ($Object | IsNullOrEmpty) } Write-Verbose "[$scriptName] - [public] - [String] - [Test-IsNotNullOrEmpty] - Done" #endregion - From [public] - [String] - [Test-IsNotNullOrEmpty] #region - From [public] - [String] - [Test-IsNullOrEmpty] Write-Verbose "[$scriptName] - [public] - [String] - [Test-IsNullOrEmpty] - Importing" filter Test-IsNullOrEmpty { <# .SYNOPSIS Test if an object is null or empty .DESCRIPTION Test if an object is null or empty .EXAMPLE '' | IsNullOrEmpty True #> [OutputType([bool])] [Cmdletbinding()] [Alias('IsNullOrEmpty')] param( # The object to test [Parameter( ValueFromPipeline, ValueFromPipelineByPropertyName )] [AllowNull()] [object] $Object ) try { if (-not ($PSBoundParameters.ContainsKey('Object'))) { Write-Debug 'Object was never passed, meaning its empty or null.' return $true } if ($null -eq $Object) { Write-Debug 'Object is null' return $true } Write-Debug "Object is: $($Object.GetType().Name)" if ($Object -eq 0) { Write-Debug 'Object is 0' return $true } if ($Object.Length -eq 0) { Write-Debug 'Object is empty array or string' return $true } if ($Object.GetType() -eq [string]) { if ([string]::IsNullOrWhiteSpace($Object)) { Write-Debug 'Object is empty string' return $true } else { Write-Debug 'Object is not an empty string' return $false } } if ($Object.Count -eq 0) { Write-Debug 'Object count is 0' return $true } if (-not $Object) { Write-Debug 'Object evaluates to false' return $true } if (($Object.GetType().Name -ne 'PSCustomObject')) { Write-Debug 'Casting object to PSCustomObject' $Object = [PSCustomObject]$Object } if (($Object.GetType().Name -eq 'PSCustomObject')) { Write-Debug 'Object is PSCustomObject' if ($Object -eq (New-Object -TypeName PSCustomObject)) { Write-Debug 'Object is similar to empty PSCustomObject' return $true } if (($Object.psobject.Properties).Count | Test-IsNullOrEmpty) { Write-Debug 'Object has no properties' return $true } } } catch { Write-Debug 'Object triggered exception' return $true } Write-Debug 'Object is not null or empty' return $false } Write-Verbose "[$scriptName] - [public] - [String] - [Test-IsNullOrEmpty] - Done" #endregion - From [public] - [String] - [Test-IsNullOrEmpty] Write-Verbose "[$scriptName] - [public] - [String] - Done" #endregion - From [public] - [String] #region - From [public] - [TLS] Write-Verbose "[$scriptName] - [public] - [TLS] - Processing folder" #region - From [public] - [TLS] - [Get-TLSConfig] Write-Verbose "[$scriptName] - [public] - [TLS] - [Get-TLSConfig] - Importing" function Get-TLSConfig { <# .SYNOPSIS Get the TLS configuration of the current session .DESCRIPTION Get the TLS configuration of the current session .EXAMPLE Get-TLSConfig Gets the TLS configuration of the current session .EXAMPLE Get-TLSConfig -ListAvailable Gets the available TLS configurations #> [OutputType(ParameterSetName = 'Default', [System.Net.SecurityProtocolType])] [OutputType(ParameterSetName = 'ListAvailable', [Array])] [CmdletBinding(DefaultParameterSetName = 'Default')] param( # List available TLS configurations [Parameter(ParameterSetName = 'ListAvailable')] [switch] $ListAvailable ) if ($ListAvailable) { return [enum]::GetValues([System.Net.SecurityProtocolType]) } return [System.Net.ServicePointManager]::SecurityProtocol } Write-Verbose "[$scriptName] - [public] - [TLS] - [Get-TLSConfig] - Done" #endregion - From [public] - [TLS] - [Get-TLSConfig] #region - From [public] - [TLS] - [Set-TLSConfig] Write-Verbose "[$scriptName] - [public] - [TLS] - [Set-TLSConfig] - Importing" function Set-TLSConfig { <# .SYNOPSIS Set the TLS configuration for the current PowerShell session .DESCRIPTION Set the TLS configuration for the current PowerShell session .EXAMPLE Set-TLSConfig -Protocol Tls12 Set the TLS configuration for the current PowerShell session to TLS 1.2 #> [OutputType([void])] [CmdletBinding(SupportsShouldProcess)] param( # The TLS protocol to enable [Parameter()] [System.Net.SecurityProtocolType[]] $Protocol = [System.Net.SecurityProtocolType]::Tls12 ) foreach ($protocolItem in $Protocol) { Write-Verbose "Enabling $protocolItem" if ($PSCmdlet.ShouldProcess("Security Protocol to [$Protocol]", 'Set')) { [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor $protocolItem } } } Write-Verbose "[$scriptName] - [public] - [TLS] - [Set-TLSConfig] - Done" #endregion - From [public] - [TLS] - [Set-TLSConfig] Write-Verbose "[$scriptName] - [public] - [TLS] - Done" #endregion - From [public] - [TLS] #region - From [public] - [URI] Write-Verbose "[$scriptName] - [public] - [URI] - Processing folder" #region - From [public] - [URI] - [ConvertTo-QueryString] Write-Verbose "[$scriptName] - [public] - [URI] - [ConvertTo-QueryString] - Importing" filter ConvertTo-QueryString { <# .SYNOPSIS Convert an object to a query string .DESCRIPTION Convert an object to a query string .EXAMPLE ConvertTo-QueryString -InputObject @{a=1;b=2} ?a=1&b=2 .EXAMPLE ConvertTo-QueryString -InputObject @{a='this is value of a';b='valueOfB'} ?a=this%20is%20value%20of%20a&b=valueOfB .EXAMPLE ConvertTo-QueryString -InputObject @{a='this is value of a';b='valueOfB'} -AsURLEncoded ?a=this+is+value+of+a&b=valueOfB #> [OutputType([string])] [CmdletBinding()] param( [Parameter( Mandatory, ValueFromPipeline )] [object] $InputObject, [Parameter()] [switch] $AsURLEncoded ) if ($InputObject -isnot [hashtable]) { $InputObject = $InputObject | ConvertTo-HashTable } $parameters = if ($AsURLEncoded) { ($InputObject.GetEnumerator() | ForEach-Object { "$([System.Web.HttpUtility]::UrlEncode($_.Key))=$([System.Web.HttpUtility]::UrlEncode($_.Value))" }) -join '&' } else { ($InputObject.GetEnumerator() | ForEach-Object { "$([System.Uri]::EscapeDataString($_.Key))=$([System.Uri]::EscapeDataString($_.Value))" }) -join '&' } if ($parameters) { '?' + $parameters } } Write-Verbose "[$scriptName] - [public] - [URI] - [ConvertTo-QueryString] - Done" #endregion - From [public] - [URI] - [ConvertTo-QueryString] #region - From [public] - [URI] - [Join-Uri] Write-Verbose "[$scriptName] - [public] - [URI] - [Join-Uri] - Importing" function Join-Uri { <# .SYNOPSIS Join a base URI with a child paths. .DESCRIPTION Join a base URI with a child paths to create a new URI. The child paths are normalized before joining with the base URI. .EXAMPLE Join-Uri -Path 'https://example.com' -ChildPath 'foo' -AdditionalChildPath 'bar' https://example.com/foo/bar Joins the base URI <https://example.com> with the child paths 'foo' and 'bar' to create the URI <https://example.com/foo/bar>. .EXAMPLE Join-Uri 'https://example.com' '/foo/' '/bar/' '//baz/something/' '/test/' <https://example.com/foo/bar/baz/something/test> Combines the base URI <https://example.com> with the child paths '/foo/', '/bar/', '//baz/something/', and '/test/'. #> [OutputType([uri])] [CmdletBinding()] param ( # The base URI to join with the child path. [Parameter(Mandatory)] [uri]$Path, # The child path to join with the base URI. [Parameter(Mandatory)] [string] $ChildPath, # Additional child paths to join with the base URI. [Parameter(ValueFromRemainingArguments)] [string[]] $AdditionalChildPath ) $segments = $ChildPath, $AdditionalChildPath $normalizedSegments = $segments | ForEach-Object { $_.Trim('/') } $uri = $Path.ToString().TrimEnd('/') + '/' + ($normalizedSegments -join '/') $uri } Write-Verbose "[$scriptName] - [public] - [URI] - [Join-Uri] - Done" #endregion - From [public] - [URI] - [Join-Uri] Write-Verbose "[$scriptName] - [public] - [URI] - Done" #endregion - From [public] - [URI] #region - From [public] - [Windows] Write-Verbose "[$scriptName] - [public] - [Windows] - Processing folder" #region - From [public] - [Windows] - [Set-WindowsSetting] Write-Verbose "[$scriptName] - [public] - [Windows] - [Set-WindowsSetting] - Importing" filter Set-WindowsSetting { <# .SYNOPSIS Set a Windows setting .DESCRIPTION Set a or multiple Windows setting(s). .NOTES Supported OS: Windows #> [CmdletBinding(SupportsShouldProcess)] param ( # Show file extensions in Windows Explorer [Parameter()] [switch] $ShowFileExtension, # Show hidden files in Windows Explorer [Parameter()] [switch] $ShowHiddenFiles ) $path = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' if ($PSCmdlet.ShouldProcess("'ShowFileExtension' to [$ShowFileExtension]", 'Set')) { $hideFileExt = if ($ShowFileExtension) { 0 } else { 1 } Set-ItemProperty -Path $path -Name HideFileExt -Value $hideFileExt } if ($PSCmdlet.ShouldProcess("'ShowHiddenFiles' to [$ShowFileExtension]", 'Set')) { $hiddenFiles = if ($ShowHiddenFiles) { 1 } else { 2 } Set-ItemProperty -Path $path -Name Hidden -Value $hiddenFiles } # Refresh File Explorer $Shell = New-Object -ComObject Shell.Application $Shell.Windows() | ForEach-Object { $_.Refresh() } } Write-Verbose "[$scriptName] - [public] - [Windows] - [Set-WindowsSetting] - Done" #endregion - From [public] - [Windows] - [Set-WindowsSetting] Write-Verbose "[$scriptName] - [public] - [Windows] - Done" #endregion - From [public] - [Windows] Write-Verbose "[$scriptName] - [public] - Done" #endregion - From [public] $exports = @{ Alias = '*' Cmdlet = '' Function = @( 'ConvertFrom-Base64String' 'ConvertTo-Base64String' 'ConvertTo-Boolean' 'Get-FileInfo' 'Remove-EmptyFolder' 'Show-FileContent' 'Clear-GitRepo' 'Invoke-GitSquash' 'Invoke-SquashBranch' 'Reset-GitRepo' 'Restore-GitRepo' 'Sync-GitRepo' 'Sync-Repo' 'Import-Variable' 'Set-GitHubEnvironmentVariable' 'Start-LogGroup' 'Stop-LogGroup' 'Search-GUID' 'Test-IsGUID' 'Convert-HashtableToString' 'Merge-Hashtable' 'Add-ModuleManifestData' 'Add-PSModulePath' 'Export-PowerShellDataFile' 'Format-ModuleManifest' 'Get-ModuleManifest' 'Invoke-PruneModule' 'Invoke-ReinstallModule' 'Set-ModuleManifest' 'Uninstall-Pester' 'Copy-Object' 'New-PSCredential' 'Restore-PSCredential' 'Save-PSCredential' 'Convert-StringCasingStyle' 'Get-StringCasingStyle' 'Split-StringByCasingStyle' 'Test-IsNotNullOrEmpty' 'Test-IsNullOrEmpty' 'Get-TLSConfig' 'Set-TLSConfig' 'ConvertTo-QueryString' 'Join-Uri' 'Set-WindowsSetting' ) Variable = '' } Export-ModuleMember @exports |