PSWinUtil.psm1

$PSWinUtil = $PSScriptRoot
$PSWinUtilRegConfDir = $PSWinUtil | Join-Path -ChildPath "resources/registry"
$PSWinUtilFunctionDir = $PSWinUtil | Join-Path -ChildPath "functions"

# Private functions
function Test-WUAdmin {
    (
        [Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::
        GetCurrent()
    ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Convert-StringToBool {
    [CmdletBinding()]
    param (
        # Specifies a path to one or more locations. Wildcards are permitted.
        [Parameter(Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName)]
        [string]
        $String
    )
    $trueStrings = @(
        'yes'
        'y'
        'true'
    )
    $falseStrings = @(
        'no'
        'n'
        'false'
    )

    if ($String -in $trueStrings) {
        return $true
    }
    if ($String -in $falseStrings) {
        return $false
    }
}

function Get-WURegistryHash {
    $registryFileName = (Get-Variable MyInvocation -Scope 1).Value.MyCommand.Name -creplace '.+-(WU)?', ''
    return . (Join-Path $PSWinUtilRegConfDir $registryFileName)
}

function Set-WURegistryFromHash {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory,
            Position = 0,
            ParameterSetName = 'LiteralPath',
            ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [hashtable]
        $RegistryHash,

        [string[]]
        $Scope,

        [string]
        $DataKey
    )

    $dataTypeParamNames = @{
        REG_SZ       = 'String'
        REG_BINARY   = 'Binary'
        REG_DWORD    = 'DWord'
        REG_QWORD    = 'QWord'
        REG_MULTI_SZ = 'Strings'
    }

    foreach ($hashKey in $RegistryHash.keys) {
        $hash = $RegistryHash.$hashKey

        if (!$Scope) {
            [string[]]$Scope = $hash.keys
        }

        foreach ($aScope in $Scope) {
            if ($DataKey -and $hash.$aScope.Data.GetType().Name -eq 'Hashtable') {
                $data = $hash.$aScope.Data.$dataKey
            }
            else {
                $data = $hash.$aScope.Data
            }
            $keyPath = ConvertTo-WUFullPath -Keyname $hash.$aScope.Keyname
            $valuename = $hash.$aScope.Valuename
            $dataType = $hash.$aScope.Type

            $registryCmdParam = @{}
            $registryCmdParam.Add('Path', $keyPath)
            $registryCmdParam.Add('Name', $valuename)
            $registryCmdParam.Add($dataTypeParamNames.$dataType, $data)

            if ($psCmdlet.ShouldProcess("Path: $keyPath Valuename: $valuename Value: $data", 'Set to registry')) {
                Set-CRegistryKeyValue @registryCmdParam
            }
        }
    }
}

function Get-WUAvailableDotnet {
    [CmdletBinding()]
    param (
    )

    $availableDotnets = @()

    $availableDotnets += [PSCustomObject]@{
        'name'    = '.NETCoreApp'
        'version' = . {
            if ((Get-Command -Name 'dotnet' -ErrorAction Ignore)) {
                # .NET Core or .NET 5+ is installed.
                dotnet --list-runtimes |
                Where-Object { $_ -clike 'Microsoft.NETCore.App*' } |
                ForEach-Object {
                    [regex]::Matches(($_ -replace '^Microsoft.NETCore.App '), '^[\d.]+') |
                    Where-Object { $_ } |
                    Select-Object -ExpandProperty Value
                } |
                Sort-Object { [System.Version]$_ } |
                Select-Object -Last 1
            }
            else {
                $null
            }
        }
    }

    $availableDotnets += [PSCustomObject]@{
        'name'    = '.NETFramework'
        'version' = . {
            dotnetversions -b |
            ForEach-Object {
                [regex]::Matches($_, '^[\d.]+') |
                Where-Object { $_ } |
                Select-Object -ExpandProperty Value
            } |
            Sort-Object { [System.Version]$_ } |
            Select-Object -Last 1
        }
    }

    $availableDotnets += [PSCustomObject]@{
        'name'    = '.NETStandard'
        'version' = . {
            $dotnetStandardVersions = @(
                [PSCustomObject]@{
                    '.NETStandard'  = '1.0'
                    '.NETCoreApp'   = '1.0'
                    '.NETFramework' = '4.5'
                }
                [PSCustomObject]@{
                    '.NETStandard'  = '1.1'
                    '.NETCoreApp'   = '1.0'
                    '.NETFramework' = '4.5'
                }
                [PSCustomObject]@{
                    '.NETStandard'  = '1.2'
                    '.NETCoreApp'   = '1.0'
                    '.NETFramework' = '4.5.1'
                }
                [PSCustomObject]@{
                    '.NETStandard'  = '1.3'
                    '.NETCoreApp'   = '1.0'
                    '.NETFramework' = '4.6'
                }
                [PSCustomObject]@{
                    '.NETStandard'  = '1.4'
                    '.NETCoreApp'   = '1.0'
                    '.NETFramework' = '4.6.1'
                }
                [PSCustomObject]@{
                    '.NETStandard'  = '1.5'
                    '.NETCoreApp'   = '1.0'
                    '.NETFramework' = '4.6.11'
                }
                [PSCustomObject]@{
                    '.NETStandard'  = '1.6'
                    '.NETCoreApp'   = '1.0'
                    '.NETFramework' = '4.6.11'
                }
                [PSCustomObject]@{
                    '.NETStandard'  = '2.0'
                    '.NETCoreApp'   = '2.0'
                    '.NETFramework' = '4.6.11'
                }
                [PSCustomObject]@{
                    '.NETStandard'  = '2.1'
                    '.NETCoreApp'   = '3.0'
                    '.NETFramework' = $null
                }
            )

            $availableDotnets |
            Where-Object { $_.version } |
            Select-Object -First 1 |
            ForEach-Object {
                $aAvailableDotnet = $_
                $dotnetStandardVersions |
                Where-Object { $null -ne $_.($aAvailableDotnet.name) } |
                Where-Object { [System.Version]$_.($aAvailableDotnet.name) -le [System.Version]$aAvailableDotnet.version } |
                Select-Object -Last 1
            } |
            Select-Object -ExpandProperty '.NETStandard'
        }
    }

    return $availableDotnets
}

# Public functions
$functionScripts = Get-ChildItem -LiteralPath $PSWinUtilFunctionDir -Recurse -File
foreach ($aFunctionScript in $functionScripts) {
    . $aFunctionScript.FullName
    Set-Alias -Name $aFunctionScript.BaseName -Value ($aFunctionScript.BaseName -replace '-', '-WU')
}

# Make NuGet package install directory
if (!$env:NUGET_PACKAGE_DIR) {
    $env:NUGET_PACKAGE_DIR = $env:USERPROFILE |
    Join-Path -ChildPath 'NuGet\packages'
}
if (!(Test-WUPathProperty -LiteralPath $env:NUGET_PACKAGE_DIR -PSProvider FileSystem -PathType Container)) {
    New-Item -Path $env:NUGET_PACKAGE_DIR -ItemType 'Directory' -Force | Out-String | Write-Verbose
}

# Resolve dependencies
$scoopBuckets = @{
    'extras'    = ''
    'yuusakuri' = 'https://github.com/yuusakuri/scoop-bucket.git'
}
$scoopDepends = @(
    @{
        CmdName = 'aria2c.exe'
        AppName = 'main/aria2'
    }
    @{
        CmdName = 'ffprobe.exe'
        AppName = 'main/ffmpeg'
    }
    @{
        CmdName = 'Set-CRegistryKeyValue'
        AppName = 'yuusakuri/carbon'
    }
    @{
        CmdName = 'dotnetversions.exe'
        AppName = 'yuusakuri/dotnetversions'
    }
) |
Where-Object { !(Get-Command -Name $_.CmdName -ErrorAction Ignore) }

$chocoDepends = @(
    @{
        CmdName = 'es.exe'
        AppName = 'Everything'
    }
) |
Where-Object { !(Get-Command -Name $_.CmdName -ErrorAction Ignore) }

$installWuAppArgs = @{
    Optimize = @()
    Force    = $true
    Unsafe   = $true
}
if ($chocoDepends) {
    $installWuAppArgs += @{
        ChocolateyPackage = $chocoDepends.AppName
    }
    $installWuAppArgs.Optimize += 'Chocolatey'
}
if ($scoopDepends) {
    $installWuAppArgs += @{
        ScoopBucket = $scoopBuckets
        ScoopApp    = $scoopDepends.AppName
    }
    $installWuAppArgs.Optimize += 'Scoop'
}

if ($chocoDepends -or $scoopDepends) {
    Install-WUApp @installWuAppArgs
}

# Pass the path to the required executable.
Add-WUPathEnvironmentVariable -LiteralPath (Get-ChildItem -LiteralPath "$PSWinUtil\tools" -Directory).FullName -Scope Process