private/cmhealthutils.ps1

function Import-CmHealthSettings {
    [CmdletBinding()]
    param (
        [parameter()][string] $Primary = "$($env:TEMP)\cmhealth.json",
        [parameter()][string] $Default = "$(Split-Path $(Get-Module cmhealth).Path)\reserve\cmhealth.json"
    )
    try {
        if (Test-Path $Primary) {
            Write-Log -Message "loading from: $Primary"
            $result = Get-Content -Path $Primary | ConvertFrom-Json
        } elseif (Test-Path $Default) {
            Write-Log -Message "loading from: $Default"
            $result = Get-Content -Path $Default | ConvertFrom-Json
        } else {
            throw "cmhealth.json was not found"
        }
    }
    catch {
        Write-Error $_.Exception.Message
    }
    finally {
        Write-Output $result
    }
}

function New-CmHealthConfig {
    [CmdletBinding()]
    param(
        [parameter(Mandatory=$False)][string]$Path = "$($env:TEMP)\cmhealth.json"
    )
    Write-Log -Message "Creating default cmhealth settings file at $Path"
    $mpath = Split-Path $(Get-Module cmhealth).Path
    $rpath = "$($mpath)\reserve"
    Write-Log -Message "source path is $rpath"
    $configFile = "$($rpath)\cmhealth.json"
    Write-Log -Message "destination path is $Path"
    Copy-Item -Path $configFile -Destination $Path -Force
    Write-Log -Message "cmhealth settings file saved as: $($Path)"
}

function Get-CmHealthDefaultValue {
    [CmdletBinding()]
    param (
        [parameter(Mandatory)][ValidateNotNullOrEmpty()][string] $KeySet,
        [parameter(Mandatory)][ValidateNotNullOrEmpty()] $DataSet
    )
    try {
        $keydef = $KeySet -split ':'
        if ($keydef.Count -gt 1) {
            $keyname = $keydef[0]
            $value   = $keydef[1]
            $result  = $DataSet."$keyname"."$value"
        } else {
            $result = $DataSet."$keydef"
        }
    }
    catch {
        Write-Error $_.Exception.Message
    }
    finally {
        Write-Output $result
    }
}

function Get-CmHealthLastTestSet {
    [CmdletBinding()]
    param(
        [parameter(Mandatory=$False)][string] $FilePath = "$($env:TEMP)\cmhealth-lastrun.txt"
    )
    if (Test-Path $FilePath) {
        Write-Log -Message "importing test selection from $FilePath"
        Write-Output $(Get-Content -Path $FilePath)
    } else {
        Write-Warning "Test history file not found: $FilePath"
    }
}

function Set-CmHealthLastTestSet {
    [CmdletBinding()]
    param(
        [parameter(Mandatory=$True)][string[]] $TestNames,
        [parameter(Mandatory=$False)][string] $FilePath = "$($env:TEMP)\cmhealth-lastrun.txt"
    )
    Write-Log -Message "saving test selection to $FilePath"
    $TestNames | Out-File -FilePath $FilePath -Force
}

function Get-CmSqlQueryResult {
    [CmdletBinding()]
    param (
        [parameter(Mandatory=$True)][ValidateNotNullOrEmpty()][string] $Query,
        [parameter(Mandatory=$True)] $Params
    )
    if ($null -ne $Params.Credential) {
        Write-Log -Message "submitting query with credentials"
        $result = @(Invoke-DbaQuery -SqlInstance $Params.SqlInstance -Database $Params.Database -Query $Query -SqlCredential $Params.Credential)
    } else {
        Write-Log -Message "submitting query without credentials"
        $result = @(Invoke-DbaQuery -SqlInstance $Params.SqlInstance -Database $Params.Database -Query $Query)
    }
    $result
}

function Get-WmiQueryResult {
    [CmdletBinding()]
    param (
        [parameter(Mandatory=$True)][string] $ClassName,
        [parameter(Mandatory=$False)][string] $Query = "",
        [parameter(Mandatory=$False)][string] $NameSpace = "root\cimv2",
        [parameter(Mandatory=$True)] $Params
    )
    if (![string]::IsNullOrEmpty($Params.Credential)) {
        Write-Log -Message "submitting WMI query with explicit credentials"
        Write-Log -Message "classname = $ClassName"
        $cs1 = New-CimSession -Credential $Params.Credential -Authentication Negotiate -ComputerName $Params.ComputerName -ErrorAction Stop
        if ([string]::IsNullOrEmpty($Query)) {
            $result = @(Get-CimInstance -CimSession $cs1 -ClassName $ClassName -Namespace $Namespace -ErrorAction Stop)
        } else {
            Write-Log -Message "query = $Query"
            $result = @(Get-CimInstance -CimSession $cs1 -ClassName $ClassName -Namespace $Namespace -Filter $Query -ErrorAction Stop)
        }
        $cs1 | Remove-CimSession
    } else {
        Write-Log -Message "submitting WMI query with implicit credentials"
        Write-Log -Message "classname = $ClassName"
        if ([string]::IsNullOrEmpty($Query)) {
            Write-Log -Message "no query. classname = $ClassName. namespace = $Namespace"
            [array]$result = Get-CimInstance -ClassName $ClassName -Namespace $Namespace -ErrorAction Stop
        } else {
            Write-Log -Message "query = $Query"
            [array]$result = Get-CimInstance -ClassName $ClassName -Namespace $Namespace -Filter $Query -ErrorAction Stop
        }
    }
    $result
}
function Get-RunTime {
    param (
        [parameter(Mandatory=$True)][datetime] $BaseTime
    )
    $NowTime = (Get-Date)
    $runTime = $(New-TimeSpan -Start $BaseTime -End $NowTime)
    $ret = $("{0}h:{1}m:{2}s:{3}ms" -f $($runTime | Foreach-Object {$_.Hours,$_.Minutes,$_.Seconds,$_.Milliseconds}))
    Write-Output $ret
}

function Convert-DecErrToHex {
    param (
        [parameter(Mandatory=$True)]$DecimalNumber
    )
    $n = [math]::Abs($DecimalNumber)
    Write-Output $('0x'+(++$n).ToString('X'))
}

# original from http://vcloud-lab.com/entries/powershell/powershell-get-registry-value-data
function Get-RegistryValueData {
    [CmdletBinding(SupportsShouldProcess=$True,
        ConfirmImpact='Medium',
        HelpURI='http://vcloud-lab.com')]
    Param ( 
        [parameter(Position=0, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
        [alias('C')]
        [String[]]$ComputerName = '.',
        [Parameter(Position=1, Mandatory=$True, ValueFromPipelineByPropertyName=$True)] 
        [alias('Hive')]
        [ValidateSet('ClassesRoot', 'CurrentUser', 'LocalMachine', 'Users', 'CurrentConfig')]
        [String]$RegistryHive = 'LocalMachine',
        [Parameter(Position=2, Mandatory=$True, ValueFromPipelineByPropertyName=$True)]
        [alias('KeyPath')]
        [String]$RegistryKeyPath = 'SYSTEM\CurrentControlSet\Services\USBSTOR',
        [parameter(Position=3, Mandatory=$True, ValueFromPipelineByPropertyName=$true)]
        [alias('Value')]
        [String]$ValueName = 'Start'
    )
    Begin {
        $RegistryRoot= "[{0}]::{1}" -f 'Microsoft.Win32.RegistryHive', $RegistryHive
        try {
            $RegistryHive = Invoke-Expression $RegistryRoot -ErrorAction Stop
        }
        catch {
            Write-Log -Message "incorrect registry hive referenced: $RegistryHive does not exist" -Category Warning -Show
        }
    }
    Process {
        Foreach ($Computer in $ComputerName) {
            Write-Log -Message "verifying connectivity to $computer"
            if ($computer -eq '.') {
                $reg = [Microsoft.Win32.RegistryKey]::OpenBaseKey($RegistryHive, 'default')
                $key = $reg.OpenSubKey($RegistryKeyPath)
                $Data = $key.GetValue($ValueName)
                [pscustomobject]@{
                    Computer = $Computer
                    RegistryValueName = "$RegistryKeyPath\$ValueName"
                    RegistryValueData = $Data
                }
            } elseif (Test-Connection $computer -Count 2 -Quiet) {
                Write-Log -Message "keypath = $RegistryKeyPath - value = $ValueName"
                $reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($RegistryHive, $Computer)
                $key = $reg.OpenSubKey($RegistryKeyPath)
                $Data = $key.GetValue($ValueName)
                [pscustomobject]@{
                    Computer = $Computer
                    RegistryValueName = "$RegistryKeyPath\$ValueName"
                    RegistryValueData = $Data
                }
            }
            else {
                Write-Log -Message "$Computer not reachable" -Category Warning -Show
            }
        }
    }
    End {
        #[Microsoft.Win32.RegistryHive]::ClassesRoot
        #[Microsoft.Win32.RegistryHive]::CurrentUser
        #[Microsoft.Win32.RegistryHive]::LocalMachine
        #[Microsoft.Win32.RegistryHive]::Users
        #[Microsoft.Win32.RegistryHive]::CurrentConfig
    }
}

# returns 4-digit ConfigMgr site version build number (e.g. "9045")
function Get-CmBuildNumber {
    [CmdletBinding()]
    param (
        [parameter(Mandatory=$False)][string]$ComputerName = '.'
    )
    $cmv = Get-RegistryValueData -ComputerName $ComputerName -RegistryHive LocalMachine -RegistryKeyPath SOFTWARE\Microsoft\SMS\Setup -ValueName 'Version'
    Write-Output $($cmv | Select-Object -ExpandProperty RegistryValueData)
}

function Get-CmVersionName {
    param(
        [parameter(Mandatory=$True)][string] $Version
    )
    Write-Log -Message "querying configuration manager version name"
    $mpath = $(Get-Module cmhealth -ListAvailable).Path | Select-Object -First 1
    $fpath = $(Join-Path -Path $(Split-Path $mpath) -ChildPath "private\buildnumbers_cm.csv")
    Write-Log -Message "reading file $fpath"
    if (Test-Path $fpath) {
        $csvdata = Import-Csv -Path $fpath
        $build = $csvdata | Where-Object {$_.Build -eq $Version} | Select-Object -ExpandProperty Name
        Write-Output $build
    } else {
        Write-Log -Message "file not found! $fpath" -Category Warning
    }
}

function Write-Log {
    param (
        [parameter(Mandatory=$False)][string]$Message = "",
        [parameter(Mandatory=$False)][string][ValidateSet('Info','Warning','Error')]$Category = "Info",
        [parameter(Mandatory=$False)][switch]$Show,
        [parameter(Mandatory=$False)][switch]$ClearLog
    )
    $msg = "$(Get-Date -f 'yyyy-MM-dd hh:mm:ss') - $Category - $Message"
    if ($ClearLog) {
        $msg | Out-File -FilePath $LogFile -Force
    } else {
        $msg | Out-File -FilePath $LogFile -Append
    }
    if ($Show) { 
        switch ($Category) {
            'Error' { Write-Host $msg -ForegroundColor Red }
            'Warning' { Write-Host $msg -ForegroundColor Yellow }
            Default { Write-Host $msg -ForegroundColor Cyan }
        }
    }
}

function Get-WindowsBuildNumber {
    param(
        [parameter(Mandatory=$True)][string] $Version
    )
    switch ($Version) {
        '10.0.10240' { '1507' }
        '10.0.10586' { '1511' }
        '10.0.14393' { '1607' }
        '10.0.15063' { '1703' }
        '10.0.16299' { '1709' }
        '10.0.17134' { '1803' }
        '10.0.17763' { '1809' }
        '10.0.18362' { '1903' }
        '10.0.18363' { '1909' }
        '10.0.19041' { '2004' }
        '10.0.19042' { '20H2' }
        '10.0.19043' { '21H1' }
        '10.0.19044' { '21H2' }
        '10.0.22000' { '21H1' }
        Default { 'unknown' }
    }
}