Scem.Support.psm1


<#
.Synopsis
   Ensures Procdump is in the appdata\local folder
.DESCRIPTION
   This cmdlet will download procdump and persist it in the appdata\local\Microsoft\Sysinternals folder.
.OUTPUTS
   Location of the procmon executable
.NOTES
   General notes
.COMPONENT
   The component this cmdlet belongs to
.ROLE
   The role this cmdlet belongs to
.FUNCTIONALITY
   Assists in capturing memory dumps.
#>

function Get-ProcdumpLocation
{
    [OutputType([System.IO.FileInfo])]

    $localSysinternalsAppData = Join-Path $env:LOCALAPPDATA "Microsoft\Sysinternals"

    $procdumpLocal = Join-Path $localSysinternalsAppData Procdump.exe

    if((-not (Test-Path $procdumpLocal))){
        if(-not (Test-Path $localSysinternalsAppData)){
            New-Item -ItemType Directory -Path $localSysinternalsAppData | Out-Null
        }

        $procdumpLocalZip = Join-Path $localSysinternalsAppData Procdump.zip
        $procdumpLiveUrl = 'https://download.sysinternals.com/files/Procdump.zip'
        Invoke-WebRequest -Uri $procdumpLiveUrl -OutFile $procdumpLocalZip

        Expand-Archive -Path $procdumpLocalZip -DestinationPath $localSysinternalsAppData
    }

    return $procdumpLocal
}

<#
.Synopsis
   Ensures Procmon is in the appdata\local folder
.DESCRIPTION
   This cmdlet will download procmon and persist it in the appdata\local folder.
.OUTPUTS
   Location of the procmon executable
.NOTES
   General notes
.COMPONENT
   The component this cmdlet belongs to
.ROLE
   The role this cmdlet belongs to
.FUNCTIONALITY
   The functionality that best describes this cmdlet
#>

function Get-ProcmonLocation
{
    [OutputType([System.IO.FileInfo])]

    $localAppData = $env:LOCALAPPDATA

    $procmonLocal = Join-Path $env:LOCALAPPDATA Procmon.exe

    if(-not (Test-Path $procmonLocal)){
        $procmonLiveUrl = 'https://live.sysinternals.com/Procmon.exe'
        Invoke-WebRequest -Uri $procmonLiveUrl -OutFile $procmonLocal
    }

    return $procmonLocal
}

<#
.Synopsis
   Short description
.DESCRIPTION
   Long description
.EXAMPLE
   Example of how to use this cmdlet
.EXAMPLE
   Another example of how to use this cmdlet
.INPUTS
   Inputs to this cmdlet (if any)
.OUTPUTS
   Output from this cmdlet (if any)
.NOTES
   General notes
.COMPONENT
   The component this cmdlet belongs to
.ROLE
   The role this cmdlet belongs to
.FUNCTIONALITY
   The functionality that best describes this cmdlet
#>

function Invoke-Procmon
{
    [CmdletBinding(DefaultParameterSetName='Parameter Set 1',
                  SupportsShouldProcess=$true,
                  PositionalBinding=$false,
                  HelpUri = 'http://www.microsoft.com/',
                  ConfirmImpact='Medium')]
    #[Alias()]
    [OutputType([System.IO.FileInfo])]
    Param
    (
        # Param1 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true,
                   ValueFromRemainingArguments=$false,
                   Position=0,
                   ParameterSetName='Parameter Set 1')]
        [ValidateNotNull()]
        $LogName,

        # Param2 help description
        [Parameter(ParameterSetName='Parameter Set 1')]
        [System.IO.FileInfo]
        $ConfigFile
    )


    Begin
    {
        Write-Information -MessageData "=== Invoke-Procmon Begin ==="
        $runtimeInSeconds = 10
        $currentLocation = Get-Location
        $procmonLocal = Get-ProcmonLocation
    }
    Process
    {
        if ($pscmdlet.ShouldProcess("Target", "Operation"))
        {
        }


        $date = Get-date
        $procmonBackingFile = Join-Path $currentLocation "$($LogName)_$($date.ToString("yyMMdd_HHmmss")).pml"
        $captureArgs = @("/AcceptEula",
                        "/Quiet",
                        "/BackingFile $procmonBackingFile"
                        "/LoadConfig $ConfigFile"
                        "/Runtime $runtimeInSeconds")


        Start-Process -FilePath $procmonLocal -ArgumentList $captureArgs -Wait

        return Get-Item -Path $procmonBackingFile
    }
    End
    {
        Write- -MessageData "=== Invoke-Procmon End ==="
    }
}

<#
.Synopsis
   Short description
.DESCRIPTION
   Long description
.EXAMPLE
   Example of how to use this cmdlet
.EXAMPLE
   Another example of how to use this cmdlet
.INPUTS
   Inputs to this cmdlet (if any)
.OUTPUTS
   Output from this cmdlet (if any)
.NOTES
   General notes
.COMPONENT
   The component this cmdlet belongs to
.ROLE
   The role this cmdlet belongs to
.FUNCTIONALITY
   The functionality that best describes this cmdlet
#>

function Invoke-ProcmonFilter
{
    [CmdletBinding(DefaultParameterSetName='Parameter Set 1',
                  SupportsShouldProcess=$true,
                  PositionalBinding=$false,
                  HelpUri = 'http://www.microsoft.com/',
                  ConfirmImpact='Medium')]
    [OutputType([System.IO.FileInfo])]
    Param
    (
        # Param1 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true,
                   ValueFromRemainingArguments=$false,
                   Position=0,
                   ParameterSetName='Parameter Set 1')]
        [ValidateNotNull()]
        [System.IO.FileInfo]
        $ProcmonLog
    )
    Begin
    {
        Write-Information -MessageData "=== Invoke-ProcmonFilter Begin ==="
        $procmonLocal = Get-ProcmonLocation
    }
    Process
    {
        if ($pscmdlet.ShouldProcess("Target", "Operation"))
        {
        }

        $filteredLog = $ProcmonLog.FullName.Replace(".pml", "-filtered.pml")

        $filterArgs = @("/AcceptEula",
                        "/Quiet",
                        "/SaveApplyFilter",
                        "/Openlog $ProcmonLog",
                        "/SaveAs $filteredLog")

        Start-Process -FilePath $procmonLocal -ArgumentList $filterArgs -Wait #-WindowStyle Hidden

        return Get-Item -Path $filteredLog
    }
    End
    {
        Write-Information -MessageData "=== Invoke-ProcmonFilter End ==="
    }
}


<#
.Synopsis
   Short description
.DESCRIPTION
   Long description
.EXAMPLE
   Example of how to use this cmdlet
.EXAMPLE
   Another example of how to use this cmdlet
#>

function Export-SCVMMInfo {
    [CmdletBinding()]
    Param(
        [parameter(Mandatory = $false)]
        [string] $ComputerName = $env:COMPUTERNAME
        )
    Begin {
        if($null -eq (Get-Module virtualmachinemanager)){
            try{
                Import-Module virtualmachinemanager -ErrorAction Stop
            }
            catch [FileNotFoundException]{
                Write-Error "This does not appear to be a System Center Virtual Machine Manager."
            }
        }

        [Microsoft.SystemCenter.VirtualMachineManager.Remoting.ServerConnection]$vmmServer = $null
        $vmmServer = virtualmachinemanager\Get-SCVMMServer -ComputerName $ComputerName
    }
    Process {
        $vmmServer | ConvertTo-Json | Out-File vmmServer.json

        $hostGroups = Get-SCVMHostGroup -VMMServer $vmmServer
        $hostGroups | ConvertTo-Json | Out-File vmmHostGroups.json

        $hosts = Get-SCVMHost -VMMServer $vmmServer
        $hosts | ConvertTo-Json | Out-File vmmHosts.json
    }
    End {
        if ($null -ne $vmmServer) {
            if ($vmmServer.IsConnected) {
                $vmmServer.Disconnect()
            }
            $vmmServer = $null
        }
    }
}

<#
.Synopsis
   Exports SCOM Management Server info
.DESCRIPTION
   Exports SCOM Management Server info to a json file.
.EXAMPLE
   Example of how to use this cmdlet
.EXAMPLE
   Another example of how to use this cmdlet
#>

function Export-SCOMManagementServerInfo
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
                   Position=0)]
        [ValidateNotNull()]
        [Microsoft.EnterpriseManagement.Administration.ManagementServer[]]
        $ManagementServer
    )

    Begin
    {
    }
    Process
    {
        # This is a hack because ConvertTo-Json does not like converting ManagmentServers.
        # When you pipe to this cmdlet, you get this error:
        # "ConvertTo-Json : An item with the same key has already been added."
        $json = $ManagementServer | ConvertTo-Csv | ConvertFrom-Csv | ConvertTo-Json
    }
    End
    {
        $json | Out-File ManagementServers.json
    }
}

<#
.Synopsis
   Returns the .NET Framework Version
.DESCRIPTION
   Returns the .NET Framework Version
#>

function Get-DotNetFrameworkVersion
{
    [CmdletBinding()]
    [OutputType([string])]
    Param
    (
    )
    # How to determine which .NET Framework security updates and hotfixes are installed
    # https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-net-framework-updates-are-installed

    $dotNetRegPath = 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\'
    $regKey = Get-ItemProperty -Path $dotNetRegPath

    [string]$FriendlyVersionValue = switch($regKey.Release)
    {
        378389 { ".NET Framework 4.5" }
        378675 { ".NET Framework 4.5.1" }
        378758 { ".NET Framework 4.5.1" }
        379893 { ".NET Framework 4.5.2" }
        393295 { ".NET Framework 4.6" }
        393297 { ".NET Framework 4.6" }
        394254 { ".NET Framework 4.6.1" }
        394271 { ".NET Framework 4.6.1" }
        394802 { ".NET Framework 4.6.2" }
        394806 { ".NET Framework 4.6.2" }
        460798 { ".NET Framework 4.7" }
        460805 { ".NET Framework 4.7" }
        461308 { ".NET Framework 4.7.1" }
        461310 { ".NET Framework 4.7.1" }
        461808 { ".NET Framework 4.7.2" }
        461814 { ".NET Framework 4.7.2" }
        528040 { ".NET Framework 4.8" }
        528049 { ".NET Framework 4.8" }
        528372 { ".NET Framework 4.8" }
        default { "Unknown .NET version: $ReleaseRegValue" }
    }

    return $FriendlyVersionValue
}

<#
.Synopsis
   Returns the .NET Framework Version
.DESCRIPTION
   Returns the .NET Framework Version
#>

function Get-DotNetFrameworkVersion2
{
    [CmdletBinding()]
    [OutputType([string])]
    Param
    (
        [parameter(Mandatory = $false)]
        [string] $ComputerName = $env:COMPUTERNAME
    )
    $fullPath = Join-Path -Path $PSScriptRoot -ChildPath Scripts\GetDotNetFrameworkVersion.ps1

    $friendlyVersion = Invoke-Command -ComputerName $ComputerName -FilePath $fullPath

    return $friendlyVersion
}

<#
.Synopsis
   Checks TLS Registry Keys and other software required for TLS 1.2 Enforcement
.DESCRIPTION
   Checks TLS Registry Keys and other software required for TLS 1.2 Enforcement
#>

Function Get-TLSInfo
{
    [CmdletBinding()]
    Param
    (
        [string[]]$Servers
    )
    BEGIN
    {
        # Blake Drumm - modified on 09/02/2021
        
        if (!$Servers)
        {
            $Servers = $env:COMPUTERNAME
        }
        
        Write-Host " Accessing Registry on:`n" -NoNewline -ForegroundColor Gray
        $scriptOut = $null
        function Inner-TLSRegKeysFunction
        {
            $finalData = @()
            $LHost = $env:computername
            $ProtocolList = "TLS 1.0", "TLS 1.1", "TLS 1.2"
            $ProtocolSubKeyList = "Client", "Server"
            $DisabledByDefault = "DisabledByDefault"
            $Enabled = "Enabled"
            $registryPath = "HKLM:\\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\"
            
            foreach ($Protocol in $ProtocolList)
            {
                
                foreach ($key in $ProtocolSubKeyList)
                {
                    #Write-Host "Checking for $protocol\$key"
                    $currentRegPath = $registryPath + $Protocol + "\" + $key
                    $IsDisabledByDefault = @()
                    $IsEnabled = @()
                    $localresults = @()
                    if (!(Test-Path $currentRegPath))
                    {
                        $IsDisabledByDefault = "Null"
                        $IsEnabled = "Null"
                    }
                    else
                    {
                        $IsDisabledByDefault = (Get-ItemProperty -Path $currentRegPath -Name $DisabledByDefault -ea 0).DisabledByDefault
                        if ($IsDisabledByDefault -eq 4294967295)
                        {
                            $IsDisabledByDefault = "0xffffffff"
                        }
                        if ($IsDisabledByDefault -eq $null)
                        {
                            $IsDisabledByDefault = "DoesntExist"
                        }
                        $IsEnabled = (Get-ItemProperty -Path $currentRegPath -Name $Enabled -ea 0).Enabled
                        if ($IsEnabled -eq 4294967295)
                        {
                            $isEnabled = "0xffffffff"
                        }
                        if ($IsEnabled -eq $null)
                        {
                            $IsEnabled = "DoesntExist"
                        }
                    }
                    $localresults = "PipeLineKickStart" | select @{ n = 'Server'; e = { $LHost } },
                                                                 @{ n = 'Protocol'; e = { $Protocol } },
                                                                 @{ n = 'Type'; e = { $key } },
                                                                 @{ n = 'DisabledByDefault'; e = { $IsDisabledByDefault } },
                                                                 @{ n = 'IsEnabled'; e = { $IsEnabled } }
                    $finalData += $localresults
                }
            }
            $results += $finaldata | select -Property * -ExcludeProperty PSComputerName, RunspaceId, PSShowComputerName | ft * -AutoSize
            
            $CrypKey1 = "HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319"
            $CrypKey2 = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\.NETFramework\v4.0.30319"
            $Strong = "SchUseStrongCrypto"
            $Crypt1 = (Get-ItemProperty -Path $CrypKey1 -Name $Strong -ea 0).SchUseStrongCrypto
            If ($crypt1 -eq 1)
            {
                $Crypt1 = $true
            }
            else
            {
                $Crypt1 = $False
            }
            $crypt2 = (Get-ItemProperty -Path $CrypKey2 -Name $Strong -ea 0).SchUseStrongCrypto
            if ($crypt2 -eq 1)
            {
                $Crypt2 = $true
            }
            else
            {
                $Crypt2 = $False
            }
            ## ODBC : https://www.microsoft.com/en-us/download/details.aspx?id=50420
            ## OLEDB : https://docs.microsoft.com/en-us/sql/connect/oledb/download-oledb-driver-for-sql-server?view=sql-server-ver15
            [string[]]$data = (Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -like "*sql*" }).name
            $odbc = $data | where { $_ -like "Microsoft ODBC Driver *" } # Need to validate version
            if ($odbc -match "11|13") { Write-Verbose "FOUND $odbc"; $odbc = "$odbc (Good)" }
            elseif ($odbc) { $odbc = $odbc }
            else { $odbc = "Not Found." }
            $oledb = $data | where { $_ -eq 'Microsoft OLE DB Driver for SQL Server' }
            if ($oledb)
            {
                Write-Verbose "Found: $oledb"
                $OLEDB = "$OLEDB (Good)"
            }
            else
            {
                $OLEDB = "Not Found."
            }
            foreach ($Protocol in $ProtocolList)
            {
                
                foreach ($key in $ProtocolSubKeyList)
                {
                    #Write-Host "Checking for $protocol\$key"
                    $currentRegPath = $registryPath + $Protocol + "\" + $key
                    $IsDisabledByDefault = @()
                    $IsEnabled = @()
                    $localresults = @()
                    if (!(Test-Path $currentRegPath))
                    {
                        $IsDisabledByDefault = "Null"
                        $IsEnabled = "Null"
                    }
                    else
                    {
                        $IsDisabledByDefault = (Get-ItemProperty -Path $currentRegPath -Name $DisabledByDefault -ea 0).DisabledByDefault
                        if ($IsDisabledByDefault -eq 4294967295)
                        {
                            $IsDisabledByDefault = "0xffffffff"
                        }
                        if ($IsDisabledByDefault -eq $null)
                        {
                            $IsDisabledByDefault = "DoesntExist"
                        }
                        $IsEnabled = (Get-ItemProperty -Path $currentRegPath -Name $Enabled -ea 0).Enabled
                        if ($IsEnabled -eq 4294967295)
                        {
                            $isEnabled = "0xffffffff"
                        }
                        if ($IsEnabled -eq $null)
                        {
                            $IsEnabled = "DoesntExist"
                        }
                    }
                    $localresults = "PipeLineKickStart" | select @{ n = 'Server'; e = { $LHost } },
                                                                 @{ n = 'Protocol'; e = { $Protocol } },
                                                                 @{ n = 'Type'; e = { $key } },
                                                                 @{ n = 'DisabledByDefault'; e = { $IsDisabledByDefault } },
                                                                 @{ n = 'IsEnabled'; e = { $IsEnabled } }
                    $finalData += $localresults
                }
            }
            ### Check if SQL Client is installed
            $RegPath = "HKLM:SOFTWARE\Microsoft\SQLNCLI11"
            IF (Test-Path $RegPath)
            {
                [string]$SQLClient11VersionString = (Get-ItemProperty $RegPath)."InstalledVersion"
                [version]$SQLClient11Version = [version]$SQLClient11VersionString
            }
            [version]$MinSQLClient11Version = [version]"11.4.7001.0"
            
            IF ($SQLClient11Version -ge $MinSQLClient11Version)
            {
                Write-Verbose "SQL Client - is installed and version: ($SQLClient11VersionString) and greater or equal to the minimum version required: (11.4.7001.0)"
                $SQLClient = "$SQLClient11Version (Good)"
            }
            ELSEIF ($SQLClient11VersionString)
            {
                Write-Verbose "SQL Client - is installed and version: ($SQLClient11VersionString) but below the minimum version of (11.4.7001.0)."
                $SQLClient = "$SQLClient11VersionString (Below minimum)"
            }
            ELSE
            {
                Write-Verbose " SQL Client - is NOT installed."
                $SQLClient = "Not Found."
            }
            ###################################################
            # Test .NET Framework version on ALL servers
            
            # Get version from registry
            $RegPath = "HKLM:SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\"
            [int]$ReleaseRegValue = (Get-ItemProperty $RegPath).Release
            # Interpret .NET version
            [string]$VersionString = switch ($ReleaseRegValue)
            {
                "378389" { ".NET Framework 4.5" }
                "378675" { ".NET Framework 4.5.1" }
                "378758" { ".NET Framework 4.5.1" }
                "379893" { ".NET Framework 4.5.2" }
                "393295" { ".NET Framework 4.6" }
                "393297" { ".NET Framework 4.6" }
                "394254" { ".NET Framework 4.6.1" }
                "394271" { ".NET Framework 4.6.1" }
                "394802" { ".NET Framework 4.6.2" }
                "394806" { ".NET Framework 4.6.2" }
                "460798" { ".NET Framework 4.7" }
                "460805" { ".NET Framework 4.7" }
                "461308" { ".NET Framework 4.7.1" }
                "461310" { ".NET Framework 4.7.1" }
                "461808" { ".NET Framework 4.7.2" }
                "461814" { ".NET Framework 4.7.2" }
                "528040" { ".NET Framework 4.8" }
                "528049" { ".NET Framework 4.8" }
                default { "Unknown .NET version: $ReleaseRegValue" }
            }
            # Check if version is 4.6 or higher
            IF ($ReleaseRegValue -ge 393295)
            {
                Write-Verbose ".NET version is 4.6 or later ($VersionString) (Good)"
                $NetVersion = "$VersionString (Good)"
                
            }
            ELSE
            {
                Write-Verbose ".NET version is NOT 4.6 or later ($VersionString) (Bad)"
                $NetVersion = "$VersionString (Does not match required version)"
            }
            $SChannelLogging = Get-ItemProperty 'HKLM:\System\CurrentControlSet\Control\SecurityProviders\SCHANNEL' -Name EventLogging | Select-Object EventLogging -ExpandProperty EventLogging
            
            $SChannelSwitch = switch ($SChannelLogging)
            {
                1 { '0x0001 - Log error messages. (Default)' }
                2 { '0x0002 - Log warnings. (Modified)' }
                3 { '0x0003 - Log warnings and error messages. (Modified)' }
                4 { '0x0004 - Log informational and success events. (Modified)' }
                5 { '0x0005 - Log informational, success events and error messages. (Modified)' }
                6 { '0x0006 - Log informational, success events and warnings. (Modified)' }
                7 { '0x0007 - Log informational, success events, warnings, and error messages (all log levels). (Modified)' }
                0 { '0x0000 - Do not log. (Modified)' }
                default { "$SChannelLogging - Unknown Log Level Possibly Misconfigured. (Modified)" }
            }
            try
            {
                $odbcODBCDataSources = Get-ItemProperty 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBC.INI\ODBC Data Sources' -ErrorAction Stop | Select-Object OpsMgrAC -ExpandProperty OpsMgrAC
            }
            catch { $odbcODBCDataSources = 'Not Found.' }
            try
            {
                $odbcOpsMgrAC = Get-ItemProperty 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBC.INI\OpsMgrAC' -ErrorAction Stop | Select-Object Driver -ExpandProperty Driver
            }
            catch { $odbcOpsMgrAC = 'Not Found.' }
            
            $additional = ('PipeLineKickStart' | Select @{ n = 'SchUseStrongCrypto'; e = { $Crypt1 } },
                                                        @{ n = 'SchUseStrongCrypto_WOW6432Node'; e = { $Crypt2 } },
                                                        @{ n = 'OLEDB'; e = { $OLEDB } },
                                                        @{ n = 'ODBC'; e = { $odbc } },
                                                        @{ n = 'ODBC (ODBC Data Sources\OpsMgrAC)'; e = { $odbcODBCDataSources } },
                                                        @{ n = 'ODBC (OpsMgrAC\Driver)'; e = { $odbcOpsMgrAC } },
                                                        @{ n = 'SQLClient'; e = { $SQLClient } },
                                                        @{ n = '.NetFramework'; e = { $NetVersion } },
                                                        @{ n = 'SChannel Logging'; e = { $SChannelSwitch } }
            )
            $results += $additional | select -Property * -ExcludeProperty PSComputerName, RunspaceId, PSShowComputerName
            
            $results += "====================================================="
            return $results
        }
    }
    
    PROCESS
    {
        foreach ($server in $servers)
        {
            Write-Host " $server" -NoNewline -ForegroundColor Cyan
            if ($server -notcontains $env:COMPUTERNAME)
            {
                $InnerTLSRegKeysFunctionScript = "function Inner-TLSRegKeysFunction { ${function:Inner-TLSRegKeysFunction} }"
                $scriptOut += (Invoke-Command -ComputerName $server -ArgumentList $InnerTLSRegKeysFunctionScript -ScriptBlock {
                        Param ($script)
                        . ([ScriptBlock]::Create($script))
                        Write-Host "-" -NoNewLine -ForegroundColor Green
                        return Inner-TLSRegKeysFunction
                    } -HideComputerName | Out-String) -replace "RunspaceId.*", ""
                Write-Host "> Completed!`n" -NoNewline -ForegroundColor Green
                
            }
            else
            {
                Write-Host "-" -NoNewLine -ForegroundColor Green
                $scriptOut += Inner-TLSRegKeysFunction
                Write-Host "> Completed!`n" -NoNewline -ForegroundColor Green
            }
        }
    }
    
    END
    {
        $scriptOut | Out-String -Width 4096
    }    
}