Utilities.psm1

[Cmdletbinding()]
param()

$scriptName = $MyInvocation.MyCommand.Name
Write-Verbose "[$scriptName] Importing subcomponents"

#region - Data import
Write-Verbose "[$scriptName] - [data] - Processing folder"
$dataFolder = (Join-Path $PSScriptRoot 'data')
Write-Verbose "[$scriptName] - [data] - [$dataFolder]"
Get-ChildItem -Path "$dataFolder" -Recurse -Force -Include '*.psd1' -ErrorAction SilentlyContinue | ForEach-Object {
    Write-Verbose "[$scriptName] - [data] - [$($_.Name)] - Importing"
    New-Variable -Name $_.BaseName -Value (Import-PowerShellDataFile -Path $_.FullName) -Force
    Write-Verbose "[$scriptName] - [data] - [$($_.Name)] - Done"
}

Write-Verbose "[$scriptName] - [data] - Done"
#endregion - Data import

#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.ps1
Write-Verbose "[$scriptName] - [/public/Base64/ConvertFrom-Base64String.ps1] - 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.ps1] - Done"
#endregion - From /public/Base64/ConvertFrom-Base64String.ps1
#region - From /public/Base64/ConvertTo-Base64String.ps1
Write-Verbose "[$scriptName] - [/public/Base64/ConvertTo-Base64String.ps1] - 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.ps1] - Done"
#endregion - From /public/Base64/ConvertTo-Base64String.ps1

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.ps1
Write-Verbose "[$scriptName] - [/public/Boolean/ConvertTo-Boolean.ps1] - 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.ps1] - Done"
#endregion - From /public/Boolean/ConvertTo-Boolean.ps1

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.ps1
Write-Verbose "[$scriptName] - [/public/Files/Get-FileInfo.ps1] - 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.ps1] - Done"
#endregion - From /public/Files/Get-FileInfo.ps1
#region - From /public/Files/Remove-EmptyFolder.ps1
Write-Verbose "[$scriptName] - [/public/Files/Remove-EmptyFolder.ps1] - Importing"

Function Remove-EmptyFolder {
    <#
        .SYNOPSIS
        Removes empty folders under the folder specified

        .DESCRIPTION
        Removes empty folders under the folder specified

        .EXAMPLE
        Remove-EmptyFolder -Path . -Verbose

        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.ps1] - Done"
#endregion - From /public/Files/Remove-EmptyFolder.ps1

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.ps1
Write-Verbose "[$scriptName] - [/public/Git/Clear-GitRepo.ps1] - 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.ps1] - Done"
#endregion - From /public/Git/Clear-GitRepo.ps1
#region - From /public/Git/Invoke-GitSquash.ps1
Write-Verbose "[$scriptName] - [/public/Git/Invoke-GitSquash.ps1] - 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'
    )

    git fetch --all --prune
    $gitHightFrom2ndCommit = [int](git rev-list --count --first-parent $BranchName) - 1
    git reset HEAD~$gitHightFrom2ndCommit
    git commit -am "$CommitMessage"
    git push --force
}

Write-Verbose "[$scriptName] - [/public/Git/Invoke-GitSquash.ps1] - Done"
#endregion - From /public/Git/Invoke-GitSquash.ps1
#region - From /public/Git/Invoke-SquashBranch.ps1
Write-Verbose "[$scriptName] - [/public/Git/Invoke-SquashBranch.ps1] - 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.ps1] - Done"
#endregion - From /public/Git/Invoke-SquashBranch.ps1
#region - From /public/Git/Reset-GitRepo.ps1
Write-Verbose "[$scriptName] - [/public/Git/Reset-GitRepo.ps1] - 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()]
    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
    git reset --hard $Upstream/$Branch

    if ($Push) {
        git push origin $Branch --force
    }
}
Set-Alias -Name Reset -Value Reset-Repo

Write-Verbose "[$scriptName] - [/public/Git/Reset-GitRepo.ps1] - Done"
#endregion - From /public/Git/Reset-GitRepo.ps1
#region - From /public/Git/Restore-GitRepo.ps1
Write-Verbose "[$scriptName] - [/public/Git/Restore-GitRepo.ps1] - 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.ps1] - Done"
#endregion - From /public/Git/Restore-GitRepo.ps1
#region - From /public/Git/Sync-GitRepo.ps1
Write-Verbose "[$scriptName] - [/public/Git/Sync-GitRepo.ps1] - 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.ps1] - Done"
#endregion - From /public/Git/Sync-GitRepo.ps1
#region - From /public/Git/Sync-Repo.ps1
Write-Verbose "[$scriptName] - [/public/Git/Sync-Repo.ps1] - 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.ps1] - Done"
#endregion - From /public/Git/Sync-Repo.ps1

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-Variables.ps1
Write-Verbose "[$scriptName] - [/public/GitHub/Import-Variables.ps1] - Importing"

filter Import-Variables {
    <#
        .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])]
    [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 -Verbose
    }
    Write-Output "$($MyInvocation.MyCommand) - $Path - Done"
}

Write-Verbose "[$scriptName] - [/public/GitHub/Import-Variables.ps1] - Done"
#endregion - From /public/GitHub/Import-Variables.ps1
#region - From /public/GitHub/New-GitHubLogGroup.ps1
Write-Verbose "[$scriptName] - [/public/GitHub/New-GitHubLogGroup.ps1] - Importing"

function New-GitHubLogGroup {
    <#
        .SYNOPSIS
        Create a new log group in GitHub Actions

        .DESCRIPTION
        Create a new log group in GitHub Actions

        .EXAMPLE
        New-GitHubLogGroup -Title 'My log group'
    #>

    [OutputType([void])]
    [CmdletBinding()]
    param (
        # Title of the log group
        [Parameter()]
        [string] $Title
    )
    Write-Output "::group::$Title"
}

Write-Verbose "[$scriptName] - [/public/GitHub/New-GitHubLogGroup.ps1] - Done"
#endregion - From /public/GitHub/New-GitHubLogGroup.ps1
#region - From /public/GitHub/Set-GitHubEnv.ps1
Write-Verbose "[$scriptName] - [/public/GitHub/Set-GitHubEnv.ps1] - Importing"

function Set-GitHubEnv {
    <#
        .SYNOPSIS
        Set a GitHub environment variable

        .DESCRIPTION
        Set a GitHub environment variable

        .EXAMPLE
        Set-GitHubEnv -Name 'MyVariable' -Value 'MyValue'
    #>

    [OutputType([void])]
    [CmdletBinding()]
    param (
        # Name of the variable
        [Parameter(Mandatory)]
        [string] $Name,

        # Value of the variable
        [Parameter(Mandatory)]
        [string] $Value
    )
    if ($PSBoundParameters.ContainsKey('Verbose')) {
        @{ "$Name" = $Value } | Format-Table -HideTableHeaders -Wrap
    }
    Write-Output "$Name=$Value" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
}

Write-Verbose "[$scriptName] - [/public/GitHub/Set-GitHubEnv.ps1] - Done"
#endregion - From /public/GitHub/Set-GitHubEnv.ps1

Write-Verbose "[$scriptName] - [/public/GitHub] - Done"
#endregion - From /public/GitHub

#region - From /public/Hashtable
Write-Verbose "[$scriptName] - [/public/Hashtable] - Processing folder"

#region - From /public/Hashtable/Merge-Hashtables.ps1
Write-Verbose "[$scriptName] - [/public/Hashtable/Merge-Hashtables.ps1] - Importing"

function Merge-Hashtables {
    <#
        .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 = ''
            ResourceGroupName = 'Main'
            Subscription = 'Main'
            ManagementGroupID = ''
            Location = 'Main'
            ModuleName = ''
            ModuleVersion = ''
        }
        $Overrides = [ordered]@{
            Action = 'overrides'
            ResourceGroupName = 'overrides'
            Subscription = 'overrides'
            ManagementGroupID = ''
            Location = 'overrides'
            ModuleName = ''
            ModuleVersion = ''
        }
        Merge-Hashtables -Main $Main -Overrides $Overrides
    #>

    [OutputType([Hashtable])]
    [CmdletBinding()]
    param (
        # Main hashtable
        [Parameter(Mandatory)]
        [hashtable] $Main,

        # Hashtable with overrides
        [Parameter(Mandatory)]
        [hashtable] $Overrides
    )
    $Output = $Main.Clone()
    ForEach ($Key in $Overrides.Keys) {
        if (($Output.Keys) -notcontains $Key) {
            $Output.$Key = $Overrides.$Key
        }
        if ($Overrides.item($Key) | IsNotNullOrEmpty) {
            $Output.$Key = $Overrides.$Key
        }
    }
    return $Output
}

Write-Verbose "[$scriptName] - [/public/Hashtable/Merge-Hashtables.ps1] - Done"
#endregion - From /public/Hashtable/Merge-Hashtables.ps1

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/Invoke-PruneModule.ps1
Write-Verbose "[$scriptName] - [/public/PowerShell/Module/Invoke-PruneModule.ps1] - 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)]" -Verbose

        $NewestModule = $UpdateableModule | Select-Object -First 1
        Write-Verbose "[$($UpdateableModuleName)] - Newest [$($NewestModule.Version -join ', ')]" -Verbose

        $OutdatedModules = $UpdateableModule | Select-Object -Skip 1
        Write-Verbose "[$($UpdateableModuleName)] - Outdated [$($OutdatedModules.Version -join ', ')]" -Verbose

        foreach ($OutdatedModule in $OutdatedModules) {
            Write-Verbose "[$($UpdateableModuleName)] - [$($OutdatedModule.Version)] - Removing" -Verbose
            $OutdatedModule | Remove-Module -Force
            Write-Verbose "[$($UpdateableModuleName)] - [$($OutdatedModule.Version)] - Uninstalling" -Verbose
            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.ps1] - Done"
#endregion - From /public/PowerShell/Module/Invoke-PruneModule.ps1
#region - From /public/PowerShell/Module/Invoke-ReinstallModule.ps1
Write-Verbose "[$scriptName] - [/public/PowerShell/Module/Invoke-ReinstallModule.ps1] - 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" -Verbose

    $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.ps1] - Done"
#endregion - From /public/PowerShell/Module/Invoke-ReinstallModule.ps1
#region - From /public/PowerShell/Module/Uninstall-Pester.ps1
Write-Verbose "[$scriptName] - [/public/PowerShell/Module/Uninstall-Pester.ps1] - 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.ps1] - Done"
#endregion - From /public/PowerShell/Module/Uninstall-Pester.ps1

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.ps1
Write-Verbose "[$scriptName] - [/public/PowerShell/Object/Copy-Object.ps1] - 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.ps1] - Done"
#endregion - From /public/PowerShell/Object/Copy-Object.ps1

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.ps1
Write-Verbose "[$scriptName] - [/public/PowerShell/PSCredential/New-PSCredential.ps1] - 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()]
    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)
    )

    $credential = New-Object -TypeName System.Management.Automation.PSCredential($Username, $Password)

    return $credential
}

Write-Verbose "[$scriptName] - [/public/PowerShell/PSCredential/New-PSCredential.ps1] - Done"
#endregion - From /public/PowerShell/PSCredential/New-PSCredential.ps1
#region - From /public/PowerShell/PSCredential/Restore-PSCredential.ps1
Write-Verbose "[$scriptName] - [/public/PowerShell/PSCredential/Restore-PSCredential.ps1] - 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.ps1] - Done"
#endregion - From /public/PowerShell/PSCredential/Restore-PSCredential.ps1
#region - From /public/PowerShell/PSCredential/Save-PSCredential.ps1
Write-Verbose "[$scriptName] - [/public/PowerShell/PSCredential/Save-PSCredential.ps1] - 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.ps1] - Done"
#endregion - From /public/PowerShell/PSCredential/Save-PSCredential.ps1

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/Search-GUID.ps1
Write-Verbose "[$scriptName] - [/public/String/Search-GUID.ps1] - 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/String/Search-GUID.ps1] - Done"
#endregion - From /public/String/Search-GUID.ps1
#region - From /public/String/Test-IsGUID.ps1
Write-Verbose "[$scriptName] - [/public/String/Test-IsGUID.ps1] - 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/String/Test-IsGUID.ps1] - Done"
#endregion - From /public/String/Test-IsGUID.ps1
#region - From /public/String/Test-IsNotNullOrEmpty.ps1
Write-Verbose "[$scriptName] - [/public/String/Test-IsNotNullOrEmpty.ps1] - 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
    #>

    [Alias('IsNotNullOrEmpty')]
    [Cmdletbinding()]
    [OutputType([bool])]
    param(
        # Object to test
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [object] $Object
    )
    return -not ($Object | IsNullOrEmpty)

}

Write-Verbose "[$scriptName] - [/public/String/Test-IsNotNullOrEmpty.ps1] - Done"
#endregion - From /public/String/Test-IsNotNullOrEmpty.ps1
#region - From /public/String/Test-IsNullOrEmpty.ps1
Write-Verbose "[$scriptName] - [/public/String/Test-IsNullOrEmpty.ps1] - Importing"

filter Test-IsNullOrEmpty {
    <#
        .SYNOPSIS
        Test if an object is null or empty

        .DESCRIPTION
        Test if an object is null or empty

        .EXAMPLE
        '' | IsNullOrEmpty -Verbose

        True
    #>

    [alias('IsNullOrEmpty')]
    [OutputType([bool])]
    [Cmdletbinding()]
    param(
        # The object to test
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [object] $Object
    )

    try {
        if ($null -eq $Object) {
            Write-Verbose 'Object is null'
            return $true
        }
        if ($Object -eq 0) {
            Write-Verbose 'Object is 0'
            return $true
        }
        if ($Object.GetType() -eq [string]) {
            if ([String]::IsNullOrWhiteSpace($Object)) {
                Write-Verbose 'Object is empty string'
                return $true
            } else {
                return $false
            }
        }
        if ($Object.count -eq 0) {
            Write-Verbose 'Object count is 0'
            return $true
        }
        if (-not $Object) {
            Write-Verbose 'Object evaluates to false'
            return $true
        }

        if (($Object.GetType().Name -ne 'pscustomobject') -or $Object.GetType() -ne [pscustomobject]) {
            Write-Verbose 'Casting object to PSCustomObject'
            $Object = [pscustomobject]$Object
        }

        if (($Object.GetType().Name -eq 'pscustomobject') -or $Object.GetType() -eq [pscustomobject]) {
            if ($Object -eq (New-Object -TypeName pscustomobject)) {
                Write-Verbose 'Object is similar to empty PSCustomObject'
                return $true
            }
            if ($Object.psobject.Properties | IsNullOrEmpty) {
                Write-Verbose 'Object has no properties'
                return $true
            }
        }
    } catch {
        Write-Verbose 'Object triggered exception'
        return $true
    }

    return $false
}

Write-Verbose "[$scriptName] - [/public/String/Test-IsNullOrEmpty.ps1] - Done"
#endregion - From /public/String/Test-IsNullOrEmpty.ps1

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.ps1
Write-Verbose "[$scriptName] - [/public/TLS/Get-TLSConfig.ps1] - 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', [Net.SecurityProtocolType])]
    [OutputType(ParameterSetName = 'ListAvailable', [Array])]
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param(
        # List available TLS configurations
        [Parameter(ParameterSetName = 'ListAvailable')]
        [switch] $ListAvailable
    )
    if ($ListAvailable) {
        return [enum]::GetValues([Net.SecurityProtocolType])
    }
    return [Net.ServicePointManager]::SecurityProtocol
}

Write-Verbose "[$scriptName] - [/public/TLS/Get-TLSConfig.ps1] - Done"
#endregion - From /public/TLS/Get-TLSConfig.ps1
#region - From /public/TLS/Set-TLSConfig.ps1
Write-Verbose "[$scriptName] - [/public/TLS/Set-TLSConfig.ps1] - 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()]
    param(
        # The TLS protocol to enable
        [Parameter()]
        [Net.SecurityProtocolType[]] $Protocol = [Net.SecurityProtocolType]::Tls12
    )

    foreach ($protocolItem in $Protocol) {
        Write-Verbose "Enabling $protocolItem"
        [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor $protocolItem
    }
}

Write-Verbose "[$scriptName] - [/public/TLS/Set-TLSConfig.ps1] - Done"
#endregion - From /public/TLS/Set-TLSConfig.ps1

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/Join-Uri.ps1
Write-Verbose "[$scriptName] - [/public/URI/Join-Uri.ps1] - 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'

        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.ps1] - Done"
#endregion - From /public/URI/Join-Uri.ps1

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/winget
Write-Verbose "[$scriptName] - [/public/Windows/winget] - Processing folder"

#region - From /public/Windows/winget/Invoke-CleanWingetApps.ps1
Write-Verbose "[$scriptName] - [/public/Windows/winget/Invoke-CleanWingetApps.ps1] - Importing"

function Invoke-CleanWingetApps {
    <#
        .SYNOPSIS
        Remove bloat-ware using winget

        .DESCRIPTION
        Remove bloat-ware using winget

        .EXAMPLE
        Invoke-CleanWingetApps

        Remove bloat-ware using winget
    #>

    [Alias('Clean-WingetApps')]
    [OutputType([void])]
    [CmdletBinding()]
    param ()

    winget source update
    winget uninstall --id Microsoft.549981C3F5F10_8wekyb3d8bbwe
    winget uninstall --id Microsoft.Getstarted_8wekyb3d8bbwe
    winget uninstall --id Microsoft.BingNews_8wekyb3d8bbwe
    winget uninstall --id Microsoft.BingWeather_8wekyb3d8bbwe
    winget uninstall --id Microsoft.GetHelp_8wekyb3d8bbwe
    winget uninstall --id Microsoft.PowerAutomateDesktop_8wekyb3d8bbwe
    winget uninstall --id Microsoft.WindowsFeedbackHub_8wekyb3d8bbwe
    winget uninstall --id Microsoft.ZuneMusic_8wekyb3d8bbwe
    winget uninstall --id Microsoft.YourPhone_8wekyb3d8bbwe
    winget uninstall --id Microsoft.People_8wekyb3d8bbwe
    winget uninstall --id Microsoft.MicrosoftEdge.Stable_8wekyb3d8bbwe
    winget uninstall --id Microsoft.GamingApp_8wekyb3d8bbwe
    winget uninstall --id Microsoft.XboxGamingOverlay_8wekyb3d8bbwe
    winget uninstall --id Microsoft.XboxGameOverlay_8wekyb3d8bbwe
    winget uninstall --id Microsoft.XboxSpeechToTextOverlay_8wekyb3d8bbwe
    winget uninstall --id Microsoft.XboxIdentityProvider_8wekyb3d8bbwe
    winget uninstall --id Microsoft.Xbox.TCUI_8wekyb3d8bbwe
    winget uninstall --id MicrosoftTeams_8wekyb3d8bbwe
    winget uninstall --id Clipchamp.Clipchamp_yxz26nhyzhsrt
    winget uninstall --id microsoft.windowscommunicationsapps_8wekyb3d8bbwe
}

Write-Verbose "[$scriptName] - [/public/Windows/winget/Invoke-CleanWingetApps.ps1] - Done"
#endregion - From /public/Windows/winget/Invoke-CleanWingetApps.ps1

Write-Verbose "[$scriptName] - [/public/Windows/winget] - Done"
#endregion - From /public/Windows/winget

#region - From /public/Windows/Show-FileExtension.ps1
Write-Verbose "[$scriptName] - [/public/Windows/Show-FileExtension.ps1] - Importing"

filter Show-FileExtension {
    <#
        .SYNOPSIS
        Show or hide file extensions in Windows Explorer

        .DESCRIPTION
        Show or hide file extensions in Windows Explorer

        .EXAMPLE
        Show-FileExtension -On

        Show file extensions in Windows Explorer

        .EXAMPLE
        Show-FileExtension -Off

        Hide file extensions in Windows Explorer

        .NOTES
        Supported OS: Windows
    #>

    [CmdletBinding(DefaultParameterSetName = 'On')]
    Param (
        # Show file extensions in Windows Explorer
        [Parameter(
            Mandatory,
            ParameterSetName = 'On'
        )]
        [switch] $On,

        # Hide file extensions in Windows Explorer
        [Parameter(
            Mandatory,
            ParameterSetName = 'Off'
        )]
        [switch] $Off
    )

    # Set a variable with the value we want to set on the registry value/subkey.
    if ($On) { $Value = 0 }
    if ($Off) { $Value = 1 }

    # Define the path to the registry key that contains the registry value/subkey
    $Path = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced'
    # Set the registry value/subkey.
    Set-ItemProperty -Path $Path -Name HideFileExt -Value $Value

    # Refresh open Explorer windows.
    # You will need to refresh the window if you have none currently open.
    # Create the Shell.Application ComObject
    $Shell = New-Object -ComObject Shell.Application
    # For each one of the open windows, refresh it.
    $Shell.Windows() | ForEach-Object { $_.Refresh() }
}

Write-Verbose "[$scriptName] - [/public/Windows/Show-FileExtension.ps1] - Done"
#endregion - From /public/Windows/Show-FileExtension.ps1
#region - From /public/Windows/Show-HiddenFiles.ps1
Write-Verbose "[$scriptName] - [/public/Windows/Show-HiddenFiles.ps1] - Importing"

function Show-HiddenFiles {
    <#
        .SYNOPSIS
        Show or hide hidden files in Windows Explorer

        .DESCRIPTION
        Show or hide hidden files in Windows Explorer

        .EXAMPLE
        Show-HiddenFiles -On

        Show hidden files in Windows Explorer

        .EXAMPLE
        Show-HiddenFiles -Off

        Hide hidden files in Windows Explorer
    #>

    [CmdletBinding(DefaultParameterSetName = 'On')]
    Param (
        # Show hidden files in Windows Explorer
        [Parameter(
            Mandatory,
            ParameterSetName = 'On'
        )]
        [switch] $On,

        # Dont show hidden files in Windows Explorer
        [Parameter(
            Mandatory,
            ParameterSetName = 'Off'
        )]
        [switch] $Off
    )
    Process {
        # Set a variable with the value we want to set on the registry value/subkey.
        if ($On) { $Value = 1 }
        if ($Off) { $Value = 2 }

        # Define the path to the registry key that contains the registry value/subkey
        $Path = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced'
        # Set the registry value/subkey.
        Set-ItemProperty -Path $Path -Name Hidden -Value $Value

        # Refresh open Explorer windows.
        # You will need to refresh the window if you have none currently open.
        # Create the Shell.Application ComObject
        $Shell = New-Object -ComObject Shell.Application
        # For each one of the open windows, refresh it.
        $Shell.Windows() | ForEach-Object { $_.Refresh() }
    }
}

Write-Verbose "[$scriptName] - [/public/Windows/Show-HiddenFiles.ps1] - Done"
#endregion - From /public/Windows/Show-HiddenFiles.ps1
#region - From /public/Windows/Test-Role.ps1
Write-Verbose "[$scriptName] - [/public/Windows/Test-Role.ps1] - Importing"

function Test-Role {
    <#
        .SYNOPSIS
        Test if the current context is running as a specified role.

        .DESCRIPTION
        Test if the current context is running as a specified role.

        .EXAMPLE
        Test-Role -Role Administrator

        Test if the current context is running as an Administrator.

        .EXAMPLE
        Test-Role -Role User

        Test if the current context is running as a User.

        .NOTES
        Supported OS: Windows
    #>

    [OutputType([Boolean])]
    [CmdletBinding()]
    [alias('Test-Admin', 'Test-Administrator', 'IsAdmin', 'IsAdministrator')]
    param(
        # Role to test
        [Parameter()]
        [Security.Principal.WindowsBuiltInRole] $Role = 'Administrator'
    )

    Write-Verbose "Test Role - [$Role]"
    $user = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($user)
    $isAdmin = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::$Role)
    Write-Verbose "Test Role - [$Role] - [$isAdmin]"
    $isAdmin
}

Write-Verbose "[$scriptName] - [/public/Windows/Test-Role.ps1] - Done"
#endregion - From /public/Windows/Test-Role.ps1

Write-Verbose "[$scriptName] - [/public/Windows] - Done"
#endregion - From /public/Windows


Write-Verbose "[$scriptName] - [/public] - Done"
#endregion - From /public

Export-ModuleMember -Function 'ConvertFrom-Base64String','ConvertTo-Base64String','ConvertTo-Boolean','Get-FileInfo','Remove-EmptyFolder','Clear-GitRepo','Invoke-GitSquash','Invoke-SquashBranch','Reset-GitRepo','Restore-GitRepo','Sync-GitRepo','Sync-Repo','Import-Variables','New-GitHubLogGroup','Set-GitHubEnv','Merge-Hashtables','Invoke-PruneModule','Invoke-ReinstallModule','Uninstall-Pester','Copy-Object','New-PSCredential','Restore-PSCredential','Save-PSCredential','Search-GUID','Test-IsGUID','Test-IsNotNullOrEmpty','Test-IsNullOrEmpty','Get-TLSConfig','Set-TLSConfig','Join-Uri','Invoke-CleanWingetApps','Show-FileExtension','Show-HiddenFiles','Test-Role' -Cmdlet '' -Variable '' -Alias ''