QCT-Update-Management.psm1

<#
.SYNOPSIS
  <Overview of script>
.DESCRIPTION
  <Brief description of script>
.PARAMETER <Parameter_Name>
    <Brief description of parameter input required. Repeat this attribute if required>
.INPUTS
  <Inputs if any, otherwise state None>
.OUTPUTS
  <Outputs if any, otherwise state None - example: Log file stored in C:\Windows\Temp\<name>.log>
.NOTES
  Version: 1.5.11
  Author: Jim.Lin
  Creation Date: 2023.03.02
  Purpose/Change: Optimize & Bugfix.
   
.EXAMPLE
  <Example goes here. Repeat this attribute for more than one example>
#>


#---------------------------------------------------------[Initialisations]--------------------------------------------------------

#Set Error Action to Silently Continue/Stop
$ErrorActionPreference = 'Stop'

#----------------------------------------------------------[Declarations]----------------------------------------------------------


#-----------------------------------------------------------[Functions]------------------------------------------------------------

function callProcessStdin($ProcessPath, $CmdArgs, $StdinArray) {
    $ErrorActionPreference = 'SilentlyContinue'
    $InformationPreference = 'SilentlyContinue'
    $WarningPreference = 'SilentlyContinue'

    $pinfo = New-Object System.Diagnostics.ProcessStartInfo
    $pinfo.FileName = $ProcessPath
    $pinfo.RedirectStandardError = $true
    $pinfo.RedirectStandardInput = $true
    $pinfo.RedirectStandardOutput = $true
    $pinfo.UseShellExecute = $false
    $pinfo.Arguments = $CmdArgs
    $p = New-Object System.Diagnostics.Process
    $p.StartInfo = $pinfo
    $p.Start() | Out-Null
    $p.WaitForExit(3 * 1000) | Out-Null

    foreach ($command in $StdinArray) {
        $p.StandardInput.WriteLine($command)
        $p.WaitForExit(1 * 1000) | Out-Null
        if ($command.IndexOf('dl -f') -ge 0) {
            $p.WaitForExit(60 * 1000) | Out-Null
        }
    }

    $stdout = $p.StandardOutput.ReadToEnd()
    return $stdout
}

function callProcess($ProcessPath, $CmdArgs) {
    $ErrorActionPreference = 'SilentlyContinue'
    $InformationPreference = 'SilentlyContinue'
    $WarningPreference = 'SilentlyContinue'

    $ASLocalWDACPolicyMode = 1
    if ($(Get-Command Get-ASLocalWDACPolicyMode -ErrorAction SilentlyContinue) -ne $null)
    {
        # to redirect the message from the verbose stream (stream #4) to the $null
        $ASLocalWDACPolicyMode = Get-ASLocalWDACPolicyMode 4> $null
    }

    if ($ASLocalWDACPolicyMode -eq 2)
    {
        $currentPath = Get-Location
        Set-Location $PSScriptRoot
        $fileName = Split-Path $ProcessPath -Leaf
        $fileName = ".\$filename"
        $result = & $fileName $CmdArgs.Split(' ')
        Set-Location $currentPath
        return $result
    }
    else
    {
        $pinfo = New-Object System.Diagnostics.ProcessStartInfo
        $pinfo.FileName = $ProcessPath
        $pinfo.RedirectStandardError = $true
        $pinfo.RedirectStandardOutput = $true
        $pinfo.UseShellExecute = $false
        $pinfo.Arguments = $CmdArgs
        $p = New-Object System.Diagnostics.Process
        $p.StartInfo = $pinfo
        $p.Start() | Out-Null
        $p.WaitForExit(3 * 1000) | Out-Null
        $stdout = $p.StandardOutput.ReadToEnd()
        return $stdout
    }
}

function callProcessWorkingDirectory($ProcessPath, $CmdArgs) {
    $ErrorActionPreference = 'SilentlyContinue'
    $InformationPreference = 'SilentlyContinue'
    $WarningPreference = 'SilentlyContinue'

    $ASLocalWDACPolicyMode = 1
    if ($(Get-Command Get-ASLocalWDACPolicyMode -ErrorAction SilentlyContinue) -ne $null)
    {
        $ASLocalWDACPolicyMode = Get-ASLocalWDACPolicyMode 4> $null
    }

    if ($ASLocalWDACPolicyMode -eq 2)
    {
        $currentPath = Get-Location
        Set-Location $PSScriptRoot
        $fileName = Split-Path $ProcessPath -Leaf
        $fileName = ".\$filename"
        $result = & $fileName $CmdArgs.Split(' ')
        Set-Location $currentPath
        return $result
    }
    else
    {
        $pinfo = New-Object System.Diagnostics.ProcessStartInfo
        $pinfo.FileName = $ProcessPath
        $pinfo.WorkingDirectory = Split-Path $ProcessPath
        $pinfo.RedirectStandardError = $true
        $pinfo.RedirectStandardOutput = $true
        $pinfo.UseShellExecute = $false
        $pinfo.Arguments = $CmdArgs
        $p = New-Object System.Diagnostics.Process
        $p.StartInfo = $pinfo
        $p.Start() | Out-Null
        $p.WaitForExit(3 * 1000) | Out-Null
        $stdout = $p.StandardOutput.ReadToEnd()
        return $stdout
    }
    
}

function Get-Driver_FW() {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $false, Position = 0)]
        [ValidateSet('BIOS', 'BMC', 'Disk', 'Expander', 'HBA', 'intelChipset', 'AMDChipset', 'NIC')]
        [string]$Device = '',
        [Parameter(Mandatory = $false, Position = 1)]
        [ValidateSet($true, $false, 1, 0)]
        $SKipCheck = $false,
        [Parameter(Mandatory = $false, Position = 2)]
        [switch]$SKipCheck2
    )

    $SKipCheck = [System.Convert]::ToBoolean($SKipCheck)
    if ((-not $SKipCheck) -or (-not $SKipCheck2)) {
        CheckVersion
    }

    $script:table2 = @()
    function addRow2($OEMDevice, $OEMName, $DriverFW, $OEMVersion, $OEMPS, $OEMDriverGUID) {
        $row2 = New-Object -TypeName PSObject
        $row2 | Add-Member -Name 'Device' -MemberType Noteproperty  -Value $OEMDevice
        $row2 | Add-Member -Name 'Name' -MemberType Noteproperty  -Value $OEMName
        $row2 | Add-Member -Name 'DriverFW' -MemberType Noteproperty  -Value $DriverFW
        $row2 | Add-Member -Name 'Version' -MemberType Noteproperty  -Value $OEMVersion
        $row2 | Add-Member -Name 'PS' -MemberType Noteproperty  -Value $OEMPS
        $row2 | Add-Member -Name 'Location' -MemberType Noteproperty  -Value $hostname   #FQDN
        $row2 | Add-Member -Name 'Location2' -MemberType Noteproperty  -Value $hostname2
        $row2 | Add-Member -Name 'ServerModel' -MemberType Noteproperty  -Value $ServerModel
        $row2 | Add-Member -Name 'DriverGUID' -MemberType Noteproperty  -Value $OEMDriverGUID
        $script:table2 += $row2
    }

    $Win32_Computersystem = Get-CimInstance Win32_Computersystem
    $ServerModel = ($Win32_Computersystem | Select-Object Model).Model
    $SysGUID = '4D36E97D-E325-11CE-BFC1-08002BE10318'
    $NICGUID = '4D36E972-E325-11CE-BFC1-08002BE10318'
    $sasGUID = '4D36E97B-E325-11CE-BFC1-08002BE10318'
    $cpu = Get-CimInstance Win32_Processor | Select-Object Name -First 1
    $Win32_PnPEntity = Get-CimInstance -Class Win32_PnPEntity

    if ($Win32_Computersystem.PartofDomain) {
        $hostname = $Win32_Computersystem.DNSHostName + '.' + $Win32_Computersystem.Domain
    } else {
        $hostname = hostname
    }
    $hostname2 = hostname

    #BIOS
    if (($Device -eq 'BIOS') -or ($Device -eq '')) {
        $BIOSVersion = (Get-CimInstance win32_bios).SMBIOSBIOSVersion
        addRow2 'BIOS' 'BIOS' 'FW' $BIOSVersion '' ''
    }
    
    #BMC
    if (($Device -eq 'BMC') -or ($Device -eq '')) {
        $PcsvDevice = Get-PcsvDevice -ErrorAction SilentlyContinue | Select-Object CurrentManagementFirmwareMajorVersion, CurrentManagementFirmwareMinorVersion
        if ($PcsvDevice) {
            $BMCVersion = $PcsvDevice.CurrentManagementFirmwareMajorVersion.ToString() + '.' + $PcsvDevice.CurrentManagementFirmwareMinorVersion.ToString()
            addRow2 'BMC' 'BMC' 'FW' $BMCVersion '' ''
        }
    }

    #Intel Chipset
    if (($Device -eq 'intelChipset') -or ($Device -eq '')) {
        $intelChipset = Get-ItemProperty -Path 'HKLM:\Software\Intel\INFInst' -Name 'Version' -ErrorAction SilentlyContinue
        if ($null -eq $intelChipset) {
            $intelChipset = ($Win32_PnPEntity | Where-Object Caption -like '*Intel*SMB*' | Get-PnpDeviceProperty | Where-Object KeyName -eq 'DEVPKEY_Device_DriverVersion' | Select-Object Data -First 1).data
        } else {
            $intelChipset = $intelChipset.Version
        }
        
        if ($intelChipset) {
            addRow2 'intelChipset' 'intelChipset' 'Driver' $intelChipset '' $SysGUID
        } else {
            if ($cpu.Name.IndexOf('Intel') -ge 0) {
                addRow2 'intelChipset' 'intelChipset' 'Driver' '' '' $SysGUID
            }
        }
    }

    #AMD Chipset
    if (($Device -eq 'AMDChipset') -or ($Device -eq '')) {
        $AMDChipset = $Win32_PnPEntity | Where-Object Caption -like '*AMD GPIO*' | Get-PnpDeviceProperty | Where-Object KeyName -eq 'DEVPKEY_Device_DriverVersion' | Select-Object Data -First 1
        if ($AMDChipset) {
            addRow2 'AMDChipset' 'AMD GPIO' 'Driver' $AMDChipset.Data '' $SysGUID
        } else {
            if ($cpu.Name.IndexOf('AMD') -ge 0) {
                addRow2 'AMDChipset' 'AMD GPIO' 'Driver' '' '' $SysGUID
            }
        }

        $AMDChipset = $Win32_PnPEntity | Where-Object Caption -like '*AMD PSP*' | Get-PnpDeviceProperty | Where-Object KeyName -eq 'DEVPKEY_Device_DriverVersion' | Select-Object Data -First 1
        if ($AMDChipset) {
            addRow2 'AMDChipset' 'AMD PSP' 'Driver' $AMDChipset.Data '' $SysGUID
        } else {
            if ($cpu.Name.IndexOf('AMD') -ge 0) {
                addRow2 'AMDChipset' 'AMD PSP' 'Driver' '' '' $SysGUID
            }
        }

        $AMDChipset = $Win32_PnPEntity | Where-Object Caption -like '*AMD CCP*' | Get-PnpDeviceProperty | Where-Object KeyName -eq 'DEVPKEY_Device_DriverVersion' | Select-Object Data -First 1
        if ($AMDChipset) {
            addRow2 'AMDChipset' 'AMD CCP' 'Driver' $AMDChipset.Data '' $SysGUID
        } else {
            if ($cpu.Name.IndexOf('AMD') -ge 0) {
                addRow2 'AMDChipset' 'AMD CCP' 'Driver' '' '' $SysGUID
            }
        }
    }

    if (($Device -eq 'NIC') -or ($Device -eq '')) {
        $NetAdapters = Get-NetAdapter
        if ($null -ne $($NetAdapters | Where-Object InterfaceDescription -like '*Mellanox*')) {
            $MellanoxFW = callProcess '$PSScriptRoot\mlxup.exe' '--query'
            if ($null -ne $MellanoxFW -and $MellanoxFW -ne '')
            {
                $MellanoxFWName = $($MellanoxFW | findstr /I /c:'Device Type').Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[2]
                $MellanoxFWVersion = $($MellanoxFW | findstr /I FW).Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]
                if ($null -ne $MellanoxFWName) {
                    addRow2 'NIC' $MellanoxFWName 'FW' $MellanoxFWVersion '' $NICGUID
                } else {
                    addRow2 'NIC' $MellanoxFWName 'FW' '' '' $NICGUID
                }
            }

            $Mellanoxs = $NetAdapters | Where-Object InterfaceDescription -Like *Mellanox* | Sort-Object InterfaceDescription | Select-Object Name, InterfaceDescription, DriverVersionString
            foreach ($Mellanox in $Mellanoxs) {
                addRow2 'NIC' $Mellanox.InterfaceDescription 'Driver' $Mellanox.DriverVersionString '' $NICGUID
                break;
            }
        }

        #Qlogic
        if ($null -ne $($NetAdapters | Where-Object InterfaceDescription -like '*Qlogic*')) {
            $QlogicFW = callProcessStdin '$PSScriptRoot\winfwnx2.exe' '' @('q')
            $QlogicFW = $QlogicFW -split "`r`n"       
            for ($i = 0; $i -lt $QlogicFW.Length; $i++) {
                if ($QlogicFW[$i].ToUpper().IndexOf('QLOGIC') -ge 0) {
                    if ($QlogicFW[$i].IndexOf(']') -ge 0) {
                        $QlogicFWName = $($QlogicFW[$i].Split(']')[1].trim() -replace '\s+ ', '_').tostring().split('_')[0]
                        $QlogicFWVersion = $($QlogicFW[$i].Split(']')[1].trim() -replace '\s+ ', '_').tostring().split('_')[1]
                        addRow2 'NIC' $QlogicFWName 'FW' $QlogicFWVersion '' $NICGUID
                    }
                }
            }
            $Qlogics = $NetAdapters | Where-Object InterfaceDescription -Like *QLogic* | Sort-Object InterfaceDescription | Select-Object Name, InterfaceDescription, DriverVersionString
            foreach ($Qlogic in $Qlogics) {
                addRow2 'NIC' $Qlogic.InterfaceDescription 'Driver' $Qlogic.DriverVersionString '' $NICGUID
                break;
            }
        }

        #Intel
        if ($null -ne $($NetAdapters | Where-Object InterfaceDescription -like '*Intel*')) {
            $IntelNics = $NetAdapters | Where-Object InterfaceDescription -Like *Intel* | Sort-Object InterfaceDescription | Select-Object Name, InterfaceDescription, DriverVersionString
            foreach ($IntelNic in $IntelNics) {
                addRow2 'NIC' $IntelNic.InterfaceDescription 'Driver' $IntelNic.DriverVersionString '' $NICGUID
                break;
            }
        }
    }

    #Storage Controller
    if (($Device -eq 'HBA') -or ($Device -eq '')) {
        $sasinfo1 = $Win32_PnPEntity | Where-Object Manufacturer -like '*AVAGO*' | Where-Object ClassGuid -like "*$sasGUID*" | Get-PnpDeviceProperty | Where-Object KeyName -eq 'DEVPKEY_Device_DriverVersion' | Select-Object Data
        if ($sasinfo1.count) {
            for ($i = 0; $i -lt $sasinfo1.count; $i++) {
                $sasController = ''
                $sasfwversion1 = ''
                $sasfwversion2 = ''
                $sasinfo = callProcess "$PSScriptRoot\sas3flash.exe" " -c $i -list"
                if (($null -ne $sasinfo) -and (-not ($sasinfo | findstr /I /c:'ERROR')))
                {
                    $sasController = $($sasinfo | findstr /I /c:'Controller ').Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
                    $sasfwversion1 = $($sasinfo | findstr /I /c:'NVDATA Version (Default)').Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
                    $sasfwversion2 = $($sasinfo | findstr /I /c:'Firmware Version').Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
                    addRow2 'Storage Controller' $sasController 'FW' $($sasfwversion1 + '_' + $sasfwversion2) '' $sasGUID
                    addRow2 'Storage Controller' $sasController 'Driver' $($sasinfo1[$i].Data) '' $sasGUID
                } else {
                    $sasinfo = callProcess "$PSScriptRoot\storcli.exe" " /c$i show"
                    if ($null -ne $sasinfo)
                    {
                        $sasController = $($($sasinfo -split "`r`n" | Select-String 'Product Name').ToString().Trim() -split '=')[1].Trim()
                        $sasfwversion1 = $($($sasinfo -split "`r`n" | Select-String 'FW Version').ToString().Trim() -split '=')[1].Trim()
                        addRow2 'Storage Controller' $sasController 'FW' $sasfwversion1 '' $sasGUID
                        addRow2 'Storage Controller' $sasController 'Driver' $sasinfo1.Data '' $sasGUID
                    }
                }
            }
        } else {
            if ($sasinfo1) {
                $sasController = ''
                $sasfwversion1 = ''
                $sasfwversion2 = ''
                $sasinfo = callProcess "$PSScriptRoot\sas3flash.exe" ' -list'
                if (($null -ne $sasinfo) -and (-not ($sasinfo | findstr /I /c:'ERROR')))
                {
                    $sasController = $($sasinfo | findstr /I /c:'Controller ').Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
                    $sasfwversion1 = $($sasinfo | findstr /I /c:'NVDATA Version (Default)').Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
                    $sasfwversion2 = $($sasinfo | findstr /I /c:'Firmware Version').Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
                    addRow2 'Storage Controller' $sasController 'FW' $($sasfwversion1 + '_' + $sasfwversion2) '' $sasGUID
                    addRow2 'Storage Controller' $sasController 'Driver' $sasinfo1.Data '' $sasGUID
                } else {
                    $sasinfo = callProcess "$PSScriptRoot\storcli.exe" ' /c0 show'
                    if ($null -ne $sasinfo)
                    {
                        $sasController = $($($sasinfo -split "`r`n" | Select-String 'Product Name').ToString().Trim() -split '=')[1].Trim()
                        $sasfwversion1 = $($($sasinfo -split "`r`n" | Select-String 'FW Version').ToString().Trim() -split '=')[1].Trim()
                        addRow2 'Storage Controller' $sasController 'FW' $sasfwversion1 '' $sasGUID
                        addRow2 'Storage Controller' $sasController 'Driver' $sasinfo1.Data '' $sasGUID
                    }
                }
            }
        }
    }

    #Expander
    if (($Device -eq 'Expander') -or ($Device -eq '')) {
        $commandlist = @"
1
show
quit
"@

        $currentPath = Get-Location
        Set-Location $PSScriptRoot
        try {
            $output = $commandlist | .\g4Xflash_x64.exe
        }
        catch {
            
        }
        if ($output | findstr /I /c:'Firmware Version') {
            $expanderinfo = ($output | findstr /I /c:'Firmware Version').Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
        }
        $expander1Name = 'Expander1'
        if ($output | findstr /I /c:'Product Id') {
            $expander1Name = $($($output -split "`r`n" | Select-String 'Product Id').ToString().Trim() -split ':')[1].Trim()
        }

        if ($output | findstr /I /C:'2)')
        {
            $commandlist = @"
2
show
quit
"@

            $output2 = $commandlist | .\g4Xflash_x64.exe
            if ($output2 | findstr /I /c:'Firmware Version') {
                $expanderinfo2 = ($output2 | findstr /I /c:'Firmware Version').Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
            }
            $expanderName2 = 'Expander2'
            if ($output2 | findstr /I /c:'Product Id') {
                $expander2Name = $($($output2 -split "`r`n" | Select-String 'Product Id').ToString().Trim() -split ':')[1].Trim()
            }
        }
        
        Set-Location $currentPath
        if ($expanderinfo) {
            if ($expanderinfo2) {
                addRow2 'Expander' $expander1Name 'FW' $expanderinfo '' ''
                addRow2 'Expander' $expander2Name 'FW' $expanderinfo2 '' ''
            } else {
                addRow2 'Expander' $expander1Name 'FW' $expanderinfo '' ''
            }
            
        }
    }

    #Disk
    if (($Device -eq 'Disk') -or ($Device -eq '')) {
        $phds = $null
        $StorageNodes = Get-StorageNode | Where-Object Name -like "$(hostname)*"
        $disks = Get-Disk | Where-Object { ($_.BootFromDisk -eq $true) -or ($_.IsBoot -eq $true) } | Select-Object UniqueId

        foreach ($StorageNode in $StorageNodes) {
            $phds += Get-PhysicalDisk -StorageNode $StorageNode -PhysicallyConnected | Where-Object { $_.UniqueId -notin $disks.UniqueId } | Where-Object FriendlyName -NotLike '*Virtual*'
        }
                
        $phds = $phds | Where-Object { $_.UniqueId -notin $disks.UniqueId } | Select-Object *
        
        $SSDs = $phds | Where-Object { $_.Mediatype -eq 'SSD' }
        foreach ($SSD in $SSDs) {
            addRow2 'Disk' $SSD.FriendlyName 'FW' $SSD.FirmwareVersion $SSD.SerialNumber ''
        }

        $HDDs = $phds | Where-Object { $_.Mediatype -eq 'HDD' }
        foreach ($HDD in $HDDs) {
            addRow2 'Disk' $HDD.FriendlyName 'FW' $HDD.FirmwareVersion $HDD.SerialNumber ''
        }
    }
    $script:table2
}

function Get-ClusterDriver_FW() {
    [CmdletBinding()]
    [OutputType([Bool])]
    Param
    (
        [Parameter(Mandatory = $false, Position = 0)]
        [ValidateSet($true, $false, 1, 0)]
        $SkipInstallPackage = $false,
        [Parameter(Mandatory = $true, Position = 1)]
        [string]$ClusterName = '',
        [Parameter(Mandatory = $false, Position = 2)]
        [ValidateSet($true, $false, 1, 0)]
        $SKipCheck = $false,
        [Parameter(Mandatory = $false, Position = 2)]
        [switch]$SKipCheck2
    )

    $SKipCheck = [System.Convert]::ToBoolean($SKipCheck)
    if ((-not $SKipCheck) -or (-not $SKipCheck2)) {
        CheckVersion
    }

    $SkipInstallPackage = [System.Convert]::ToBoolean($SkipInstallPackage)

    $ClusterNodes = Get-ClusterNode -Cluster $ClusterName

    if (-not $SkipInstallPackage) {
        foreach ($ClusterNode in $ClusterNodes) {
            Invoke-Command -ComputerName $ClusterNode -ScriptBlock {
                Install-PackageProvider Nuget -Force -MinimumVersion 2.8.5.201 > $null
                Install-Module -Name PowershellGet -SkipPublisherCheck -AllowClobber -Force -Confirm:$false > $null
                Install-Module QCT-Update-Management -SkipPublisherCheck -AllowClobber -Force > $null
                Import-Module QCT-Update-Management -Force > $null
            } | Out-Null
        }
    }

    foreach ($CheckItem in $('BIOS', 'BMC', 'intelChipset', 'AMDChipset', 'NIC', 'HBA', 'Expander', 'Disk')) {
        Write-Host $CheckItem
        foreach ($ClusterNode in $ClusterNodes) {    
            $Driver_FWs = Invoke-Command -ComputerName $ClusterNode -ScriptBlock {
                param($CheckItem)
                Get-Driver_FW -Device $CheckItem
            } -ArgumentList $CheckItem
            foreach ($Driver_FW in $Driver_FWs) {
                Write-Host "$ClusterNode $CheckItem-$($Driver_FW.Name)_$($Driver_FW.DriverFW):" $Driver_FW.Version
            }
        }
        Write-Host '----------------------------------------------------------------'
    }
}

function Enable-DiskLocate() {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$SerialNumber = ''
    )
    # CheckVersion

    $allController = callProcess "$PSScriptRoot\storcli.exe" 'show ctrlcount'
    $ctrlCount = $($($allController -split "`r`n" | Select-String -Pattern 'Controller') -split '=')[1].ToString().Trim()
    for ($i = 0; $i -lt $ctrlCount; $i++) {
        $alldiskStr = callProcess "$PSScriptRoot\storcli.exe" "/c$i show all"
        $diskStr = $alldiskStr -split "`r`n" | Select-String -Pattern $SerialNumber -Context 6
        if ($diskStr -eq $null)
        {
            $alldiskStr = callProcess "$PSScriptRoot\storcli.exe" "/c$i/eall/sall show all"
            $diskStr = $alldiskStr -split "`r`n" | Select-String -Pattern $SerialNumber -Context 6
        }
        if ($diskStr) {
            $disklocation = $($($diskStr.ToString() -split "`r`n" | Select-String 'Drive /').ToString().Trim() -split ' ')[1]
            if ($disklocation) {      
                return callProcess "$PSScriptRoot\storcli.exe" "$disklocation start locate"
            }
        }
    }
    
    return 'Failed.'
}

function Disable-DiskLocate() {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $false, Position = 0)]
        [string]$SerialNumber = ''
    )
    # CheckVersion

    $allController = callProcess "$PSScriptRoot\storcli.exe" 'show ctrlcount'
    $ctrlCount = $($($allController -split "`r`n" | Select-String -Pattern 'Controller') -split '=')[1].ToString().Trim()
    for ($i = 0; $i -lt $ctrlCount; $i++) {
        $alldiskStr = callProcess "$PSScriptRoot\storcli.exe" "/c$i show all"
        $diskStr = $alldiskStr -split "`r`n" | Select-String -Pattern $SerialNumber -Context 6
        if ($diskStr -eq $null)
        {
            $alldiskStr = callProcess "$PSScriptRoot\storcli.exe" "/c$i/eall/sall show all"
            $diskStr = $alldiskStr -split "`r`n" | Select-String -Pattern $SerialNumber -Context 6
        }
        if ($diskStr) {
            $disklocation = $($($diskStr.ToString() -split "`r`n" | Select-String 'Drive /').ToString().Trim() -split ' ')[1]
            if ($disklocation) {      
                return callProcess "$PSScriptRoot\storcli.exe" "$disklocation stop locate"
            }
        }
    }
    
    return 'Failed.'
}

function checkModel($Model) {
    $Brand = 'unknow'
    $Series = 'unknow'
    if ($Model -like '*INTEL*') {
        $Brand = 'INTEL'
    } elseif ($Model -like '*SAMSUNG*') {
        $Brand = 'SAMSUNG'
    } elseif ($Model -like '*TOSHIBA*') {
        $Brand = 'TOSHIBA'
    } else {
        $Brand = 'unknow'
    }

    if ($Brand -eq 'INTEL') {
        if ($Model -like '*SSDSC2BA*4*') {
            $Series = 'INTEL_S3710'
        } elseif ($Model -like '*SSDSC2BB*6*') {
            $Series = 'INTEL_S3510'
        } elseif (($Model -like '*SSDSC2BB*7*') -or ($Model -like '*SSDSCKJB*7*')) {
            $Series = 'INTEL_S3520'
        } 
        elseif ($Model -like '*SSDSC2KB*7*') {
            $Series = 'INTEL_S4500'
        } 
        elseif ($Model -like '*SSDSC2KB*8*') {
            $Series = 'INTEL_D3-S4510'
        } 
        elseif ($Model -like '*SSDSC2KG*7*') {
            $Series = 'INTEL_S4600'
        } 
        elseif ($Model -like '*SSDSC2KG*8*') {
            $Series = 'INTEL_D3-S4610'
        } 
        elseif ($Model -like '*SSDPEKKA512G7*') {
            $Series = 'INTEL_P3100'
        } 
        elseif (($Model -like '*SSDPE2MD*4*') -or ($Model -like '*SSDPEDMD*4*')) {
            $Series = 'INTEL_P3700'
        } 
        elseif (($Model -like '*SSDPEDKX*7*') -or ($Model -like '*SSDPE2KX*7*')) {
            $Series = 'INTEL_P4500'
        } 
        elseif ($Model -like '*SSDPE7KX*7*') {
            $Series = 'INTEL_P4501'
        } 
        elseif ($Model -like '*SSDPE2KX*8*') {
            $Series = 'INTEL_P4510'
        } 
        elseif ($Model -like '*SSDPELKX*8*') {
            $Series = 'INTEL_P4511'
        } 
        elseif (($Model -like '*SSDPE2KE*7*') -or ($Model -like '*SSDPEDKE*7*')) {
            $Series = 'INTEL_P4600'
        } 
        elseif ($Model -like '*SSDPE7KE*7*') {
            $Series = 'INTEL_P4601'
        } 
        elseif ($Model -like '*SSDPE2KE*8*') {
            $Series = 'INTEL_P4610'
        } 
        elseif (($Model -like '*SSDPED1K*A*') -or ($Model -like '*SSDPE21K*A*')) {
            $Series = 'INTEL_P4800X'
        } 
        else {
            $Series = 'unknow'
        }
    }
    elseif ($Brand -eq 'SAMSUNG') {
        if ($Model -like '*MZ7KM*HA*') {
            $Series = 'SAMSUNG_SM863'
        } 
        elseif ($Model -like '*MZ7KM*HM*') {
            $Series = 'SAMSUNG_SM863a'
        } 
        elseif ($Model -like '*MZ7KH*HA*') {
            $Series = 'SAMSUNG_SM883'
        } 
        elseif ($Model -like '*MZQLB*H*') {
            $Series = 'SAMSUNG_PM983'
        } 
        elseif ($Model -like '*MZQLW*H*') {
            $Series = 'SAMSUNG_PM963'
        } 
        elseif ($Model -like '*MZ7LH*H*') {
            $Series = 'SAMSUNG_PM883'
        } 
        elseif ($Model -like '*MZ7LM*H*') {
            $Series = 'SAMSUNG_PM863a'
        } 
        elseif (($Model -like '*MZPLL*HMLA*') -or ($Model -like '*MZPLL*HAJQ*') -or ($Model -like '*MZWLL*HMLA*') -or ($Model -like '*MZWLL*HAJQ*')) {
            $Series = 'SAMSUNG_PM1725b'
        } 
        elseif (($Model -like '*MZPLL*HMLS*') -or ($Model -like '*MZPLL*HEHP*') -or ($Model -like '*MZWLL*HEHP*') -or ($Model -like '*MZWLL*HMJP*') -or ($Model -like '*MZWLL*HMLS*')) {
            $Series = 'SAMSUNG_PM1725a'
        } 
        elseif ($Model -like '*MZILT*H*') {
            $Series = 'SAMSUNG_PM1643'
        } 
        elseif ($Model -like '*MZILS*H*') {
            $Series = 'SAMSUNG_PM1633a'
        } 
        else {
            $Series = 'unknow'
        }
    } 
    elseif ($Brand -eq 'TOSHIBA') {
        if ($Model -like '*THNSNJ800PCSZ*') {
            $Series = 'Toshiba_HK3E2'
        } 
        else {
            $Series = 'unknow'
        }
    } 
    else {
        if ($Model -like '*ST8000*') {
            $Series = 'SEAGATE_Archive_HDD_v2'
        } 
        else {
            $Series = 'unknow'
        }
    }
    return $Series
}

function checkLifetime($smartInfo) {
    $Wear_Leveling_Count = 0
    $Wear_Leveling_Count_Threshold = 0
    if ($null -ne $($smartInfo -split "`r`n" | Select-String 'Wear_Leveling_Count')) {
        # Samsung SSD 99 to 0
        $Wear_Leveling_Count = [int] $($smartInfo -split "`r`n" | Select-String 'Wear_Leveling_Count').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[3].Trim()
        $Wear_Leveling_Count_Threshold = [int] $($smartInfo -split "`r`n" | Select-String 'Wear_Leveling_Count').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[5].Trim()
    } 
    elseif ($null -ne $($smartInfo -split "`r`n" | Select-String 'Media_Wearout_Indicator')) {
        # INTEL SSD 100 to 1
        $Wear_Leveling_Count = $($smartInfo -split "`r`n" | Select-String 'Media_Wearout_Indicator').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[3].Trim()
        $Wear_Leveling_Count_Threshold = $($smartInfo -split "`r`n" | Select-String 'Media_Wearout_Indicator').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[5].Trim()
    } 
    elseif ($null -ne $($smartInfo -split "`r`n" | Select-String 'Available Spare:')) {
        #INTEL NVME
        $Wear_Leveling_Count = $($smartInfo -split "`r`n" | Select-String 'Available Spare:').ToString().Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim().Replace('%', '')
        $Wear_Leveling_Count_Threshold = $($smartInfo -split "`r`n" | Select-String 'Available Spare Threshold:').ToString().Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim().Replace('%', '')
    } 
    else {
        $Wear_Leveling_Count = 'none'
        $Wear_Leveling_Count_Threshold = 'none'
    }
    $Wear_Leveling_Count
    $Wear_Leveling_Count_Threshold
}

function checkPoweronHour($smartInfo) {
    if ($smartInfo -split "`r`n" | Select-String 'Power_On_Hours') {
        $Power_On_Hours = $($smartInfo -split "`r`n" | Select-String 'Power_On_Hours').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[9].Trim()
    } 
    elseif ($smartInfo -split "`r`n" | Select-String 'Power On Hours:') {
        $Power_On_Hours = $($smartInfo -split "`r`n" | Select-String 'Power On Hours:').ToString().Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
    } 
    else {
        $Power_On_Hours = 'none'
    }

    if ($smartInfo -split "`r`n" | Select-String 'Power_Cycle_Count') {
        $Power_Cycle_Count = $($smartInfo -split "`r`n" | Select-String 'Power_Cycle_Count').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[9].Trim()
    } 
    elseif ($smartInfo -split "`r`n" | Select-String 'Power Cycles:') {
        $Power_Cycle_Count = $($smartInfo -split "`r`n" | Select-String 'Power Cycles:').ToString().Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
    } 
    else {
        $Power_Cycle_Count = 'none'
    }
    $Power_On_Hours
    $Power_Cycle_Count
}

function checkUserCapacity($smartInfo) {
    if ($smartInfo -split "`r`n" | Select-String 'User Capacity') {
        $userCapacity = $($smartInfo -split "`r`n" | Select-String 'User Capacity').ToString().Split('[', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim().Replace(']', '').Replace(' ', '_')
    } 
    elseif ($smartInfo -split "`r`n" | Select-String 'Namespace 1 Size/Capacity') {
        $userCapacity = $($smartInfo -split "`r`n" | Select-String 'Namespace 1 Size/Capacity').ToString().Split('[', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim().Replace(']', '').Replace(' ', '_')
    } 
    else {
        $userCapacity = 'none'
    }
    $userCapacity
}

function writeHostColored() {
    [CmdletBinding(ConfirmImpact = 'None', SupportsShouldProcess = $false, SupportsTransactions = $false)]
    param(
        [Parameter(Position = 0, ValueFromPipeline = $true)]
        [string[]] $Text
        ,
        [switch] $NoNewline
        ,
        [ConsoleColor] $BackgroundColor = $host.UI.RawUI.BackgroundColor
        ,
        [ConsoleColor] $ForegroundColor = $host.UI.RawUI.ForegroundColor
    )

    begin {
        if ($null -ne $Text) {
            $Text = "$Text"
        }
    }

    process {
        if ($Text) {
            $curFgColor = $ForegroundColor
            $curBgColor = $BackgroundColor
            $tokens = $Text.split('#')
            $prevWasColorSpec = $false
            foreach ($token in $tokens) {

                if (-not $prevWasColorSpec -and $token -match '^([a-z]+)(:([a-z]+))?$') {
                    # a potential color spec.
                    try {
                        $curFgColor = [ConsoleColor]  $matches[1]
                        $prevWasColorSpec = $true
                    }
                    catch {}
                    if ($matches[3]) {
                        try {
                            $curBgColor = [ConsoleColor]  $matches[3]
                            $prevWasColorSpec = $true
                        }
                        catch {}
                    }

                    if ($prevWasColorSpec) {
                        continue                    
                    }
                }

                $prevWasColorSpec = $false

                if ($token) {
                    $argsHash = @{}
                    if ([int] $curFgColor -ne -1) { $argsHash += @{ 'ForegroundColor' = $curFgColor } }

                    if ([int] $curBgColor -ne -1) { $argsHash += @{ 'BackgroundColor' = $curBgColor } }
                    Write-Host -NoNewline @argsHash $token
                }

                $curFgColor = $ForegroundColor
                $curBgColor = $BackgroundColor

            }
        }
        if (-not $NoNewLine) { write-host }
    }
}

function Get-SmartInfo() {
    [CmdletBinding()]
    [OutputType([Bool])]
    Param
    (
        [Parameter(Mandatory = $false, Position = 0)]
        [ValidateSet($true, $false, 1, 0)]
        $Color = $false
    )
    # CheckVersion

    $Color = [System.Convert]::ToBoolean($Color)
    $smartPath = "$PSScriptRoot\smartctl.exe"
    $disks = callProcess $smartPath '--scan' 1
    $diskArray = @()

    foreach ($disk in $($disks -split "`r`n")) {
        if ($($disk | select-string '/dev/')) {
            $disk = $($disk -split ' ' )[0]
            $smartInfo = callProcess $smartPath " -A -i -H $disk -s on" 0.2
            if (-not $($smartInfo -split "`r`n" | Select-String 'Open failed')) {
                if (($smartInfo -split "`r`n" | Select-String 'SMART overall-health self-assessment test result'))
                {
                    $smartResult = $($smartInfo -split "`r`n" | Select-String 'SMART overall-health self-assessment test result').ToString().Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
                }
                else
                {
                    continue
                }

                if ($smartInfo -split "`r`n" | Select-String 'Sector Size') {
                    $sectorSize = [int] $($smartInfo -split "`r`n" | Select-String 'Sector Size').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[2]
                } 
                else {
                    $sectorSize = [int] 512
                }

                $Model = $($smartInfo -split "`r`n" | Select-String 'Device Model').ToString().Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim().ToUpper().Replace(' ', '_')
                $serialNumber = $($smartInfo -split "`r`n" | Select-String 'Serial Number').ToString().Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
                                 
                $return = checkPoweronHour $smartInfo
                $Power_On_Hours = $return[0]
                $Power_Cycle_Count = $return[1]

                $userCapacity = checkUserCapacity $smartInfo                
            
                $Series = checkModel $Model
        
                $Reallocated_Sector_Ct = 0
                $Current_Pending_Sector = 0
                $Offline_Uncorrectable = 0
                if ($null -ne $($smartInfo -split "`r`n" | Select-String 'Reallocated_Sector_Ct')) {
                    $Reallocated_Sector_Ct = [int] $($smartInfo -split "`r`n" | Select-String 'Reallocated_Sector_Ct').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[9].Replace('[', '')
                }

                if ($null -ne $($smartInfo -split "`r`n" | Select-String 'Current_Pending_Sector')) {
                    $Current_Pending_Sector = [int] $($smartInfo -split "`r`n" | Select-String 'Current_Pending_Sector').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[9].Replace('[', '')
                }

                if ($null -ne $($smartInfo -split "`r`n" | Select-String 'Offline_Uncorrectable')) {
                    $Offline_Uncorrectable = [int] $($smartInfo -split "`r`n" | Select-String 'Offline_Uncorrectable').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[9].Replace('[', '') 
                }
                
                if ($null -ne $($smartInfo -split "`r`n" | Select-String 'Command_Timeout')) {
                    $Command_Timeout = [long] $($smartInfo -split "`r`n" | Select-String 'Command_Timeout').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[9].Replace('[', '') 
                }

                if ($null -ne $($smartInfo -split "`r`n" | Select-String 'Spin_Retry_Count')) {
                    $Spin_Retry_Count = [int] $($smartInfo -split "`r`n" | Select-String 'Spin_Retry_Count').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[9].Replace('[', '') 
                }

                if ($null -ne $($smartInfo -split "`r`n" | Select-String 'End-to-End_Error')) {
                    $End_to_End_Error = [int] $($smartInfo -split "`r`n" | Select-String 'End-to-End_Error').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[9].Replace('[', '') 
                }                

                $smartCaution = ''
                if ($Reallocated_Sector_Ct + $Current_Pending_Sector + $Offline_Uncorrectable + $Spin_Retry_Count + $End_to_End_Error -gt 0) {
                    $smartResult = 'Caution'
                    if ($Reallocated_Sector_Ct -gt 0) {
                        $smartCaution += 'Reallocated Sector Ct:' + $Reallocated_Sector_Ct + '`n'
                    }

                    if ($Current_Pending_Sector -gt 0) {
                        $smartCaution += 'Current Pending Sector:' + $Current_Pending_Sector + '`n'
                    }

                    if ($Offline_Uncorrectable -gt 0) {
                        $smartCaution += 'Offline Uncorrectable:' + $Offline_Uncorrectable + '`n'
                    }
                    
                    if ($Spin_Retry_Count -gt 0) {
                        $smartCaution += 'Spin Retry Count:' + $Spin_Retry_Count + '`n'
                    }
                    
                    if ($End_to_End_Error -gt 0) {
                        $smartCaution += 'End-to-End error:' + $End_to_End_Error + '`n'
                    }
                    
                    if ($Command_Timeout -gt 0) {
                        $smartCaution += 'Command Timeout:' + $Command_Timeout + '`n'
                    }
                }

                if (($smartInfo -split "`r`n") -like '241 *')
                {
                    $Total_LBAs_Written = [long] $(($smartInfo -split "`r`n") -like '241 *').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[9]
                }
                else
                {
                    $Total_LBAs_Written = ''
                }

                $Total_Host_Write = 0
                if (($Model.IndexOf('INTEL') -ge 0) -or ($Model.IndexOf('TOSHIBA') -ge 0)) {
                    $Total_Host_Write = '{0:n2}' -f $($Total_LBAs_Written * 32 / 1024)  #GB
                } 
                else {
                    $Total_Host_Write = '{0:N2}' -f $($Total_LBAs_Written * $($sectorSize / 1024) / 1024 / 1024)  #GB
                }

                if ($Total_Host_Write -eq '0.00') {
                    $Total_Host_Write = 'N/A'
                } 
                else {
                    $Total_Host_Write = "$Total_Host_Write GB"
                }

                $return = checkLifetime $smartInfo
                $Wear_Leveling_Count = $return[0]
                $Wear_Leveling_Count_Threshold = $return[1]

                if ($Color -and ($smartResult -ne 'PASSED')) {
                    writeHostColored "$disk $Series $userCapacity $Model #yellow#$smartResult# $serialNumber $Total_Host_Write $Wear_Leveling_Count $Wear_Leveling_Count_Threshold $Power_On_Hours $Power_Cycle_Count"
                }
             
                $diskObj = New-Object -TypeName PSObject
                $diskObj | Add-Member -Name 'Name' -MemberType Noteproperty  -Value $disk
                $diskObj | Add-Member -Name 'Series' -MemberType Noteproperty  -Value $Series
                $diskObj | Add-Member -Name 'userCapacity' -MemberType Noteproperty  -Value $disk
                $diskObj | Add-Member -Name 'Model' -MemberType Noteproperty  -Value $Model
                $diskObj | Add-Member -Name 'smartResult' -MemberType Noteproperty  -Value $smartResult

                $diskObj | Add-Member -Name 'serialNumber' -MemberType Noteproperty  -Value $serialNumber
                $diskObj | Add-Member -Name 'Total_Host_Write' -MemberType Noteproperty  -Value $Total_Host_Write
                $diskObj | Add-Member -Name 'Wear_Leveling_Count' -MemberType Noteproperty  -Value $Wear_Leveling_Count
                $diskObj | Add-Member -Name 'Wear_Leveling_Count_Threshold' -MemberType Noteproperty  -Value $Wear_Leveling_Count_Threshold
                $diskObj | Add-Member -Name 'Power_On_Hours' -MemberType Noteproperty  -Value $Power_On_Hours                             

                $diskObj | Add-Member -Name 'Power_Cycle_Count' -MemberType Noteproperty  -Value $Power_Cycle_Count
                $diskObj | Add-Member -Name 'SmartCaution' -MemberType Noteproperty  -Value $smartCaution   
                $diskArray += $diskObj
            }
        }
    }

    $disks = callProcess $smartPath '-d nvme --scan' 1

    foreach ($disk in $($disks -split "`r`n")) {
        if ($($disk | Select-String '/dev/')) {
            $disk = $($disk -split ' ' )[0]
            $smartInfo = callProcess $smartPath " -A -i -H $disk -s on" 0.3

            if (($smartInfo -split "`r`n" | Select-String 'SMART overall-health self-assessment test result'))
            {
                $smartResult = $($smartInfo -split "`r`n" | Select-String 'SMART overall-health self-assessment test result').ToString().Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
            }
            else
            {
                continue
            }

            $sectorSize = [int] 512 # $($smartInfo -split "`r`n" | Select-String 'Sector Size').ToString().Split(' ',[System.StringSplitOptions]::RemoveEmptyEntries)[2]

            $Model = $($smartInfo -split "`r`n" | Select-String 'Model').ToString().Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim().ToUpper().Replace(' ', '_')
            $serialNumber = $($smartInfo -split "`r`n" | Select-String 'Serial Number').ToString().Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
            
            $return = checkPoweronHour $smartInfo
            $Power_On_Hours = $return[0]
            $Power_Cycle_Count = $return[1]

            $userCapacity = checkUserCapacity $smartInfo

            $Series = checkModel $Model

            if ($($smartInfo -split "`r`n" | Select-String 'Data Units Written').ToString().IndexOf('PB') -ge 0) {
                $Data_Units_Written = '{0:n2}' -f $(1024 * 1024 * [int] $($smartInfo -split "`r`n" | Select-String 'Data Units Written').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[4].Replace('[', ''))
            } 
            elseif ($($smartInfo -split "`r`n" | Select-String 'Data Units Written').ToString().IndexOf('TB') -ge 0) {
                $Data_Units_Written = '{0:n2}' -f $(1024 * [int] $($smartInfo -split "`r`n" | Select-String 'Data Units Written').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[4].Replace('[', ''))
            } 
            elseif ($($smartInfo -split "`r`n" | Select-String 'Data Units Written').ToString().IndexOf('GB') -ge 0) {
                $Data_Units_Written = '{0:n2}' -f $([int] $($smartInfo -split "`r`n" | Select-String 'Data Units Written').ToString().Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[4].Replace('[', ''))
            }

            $smartCaution = ''
            $return = checkLifetime $smartInfo
            $Wear_Leveling_Count = $return[0]
            $Wear_Leveling_Count_Threshold = $return[1]

            if ($Color -and ($smartResult -ne 'PASSED')) {
                writeHostColored "$disk $Series $userCapacity $Model #yellow#$smartResult# $serialNumber $Data_Units_Written $Wear_Leveling_Count $Wear_Leveling_Count_Threshold $Power_On_Hours $Power_Cycle_Count"
            }

            $diskObj = New-Object -TypeName PSObject
            $diskObj | Add-Member -Name 'Name' -MemberType Noteproperty  -Value $disk
            $diskObj | Add-Member -Name 'Series' -MemberType Noteproperty  -Value $Series
            $diskObj | Add-Member -Name 'userCapacity' -MemberType Noteproperty  -Value $userCapacity
            $diskObj | Add-Member -Name 'Model' -MemberType Noteproperty  -Value $Model
            $diskObj | Add-Member -Name 'smartResult' -MemberType Noteproperty  -Value $smartResult

            $diskObj | Add-Member -Name 'serialNumber' -MemberType Noteproperty  -Value $serialNumber
            $diskObj | Add-Member -Name 'Total_Host_Write' -MemberType Noteproperty  -Value "$Data_Units_Written GB"
            $diskObj | Add-Member -Name 'Wear_Leveling_Count' -MemberType Noteproperty  -Value $Wear_Leveling_Count
            $diskObj | Add-Member -Name 'Wear_Leveling_Count_Threshold' -MemberType Noteproperty  -Value $Wear_Leveling_Count_Threshold
            $diskObj | Add-Member -Name 'Power_On_Hours' -MemberType Noteproperty  -Value $Power_On_Hours
            
            $diskObj | Add-Member -Name 'Power_Cycle_Count' -MemberType Noteproperty  -Value $Power_Cycle_Count
            $diskObj | Add-Member -Name 'SmartCaution' -MemberType Noteproperty  -Value $smartCaution
            $diskArray += $diskObj
        }
    }
    $diskArray
}

function Update-OEMFirmware-LSI {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$FwPath = '',
        [Parameter(Mandatory = $true, Position = 1)]
        [string]$Mptsas = '',
        [Parameter(Mandatory = $true, Position = 2)]
        [string]$Mptx64 = ''
    )
    # CheckVersion

    $script:tableFW = @()
    function addRowFW($FWType, $FWVersion, $FWVersionNew) {
        $row2 = New-Object -TypeName PSObject
        $row2 | Add-Member -Name 'Type' -MemberType Noteproperty  -Value $FWType
        $row2 | Add-Member -Name 'FWVersion' -MemberType Noteproperty  -Value $FWVersion
        $row2 | Add-Member -Name 'FWVersionNew' -MemberType Noteproperty  -Value $FWVersionNew

        $script:tableFW += $row2
    }

    $OEMStorageControllPath = $PSScriptRoot

    $sasinfo = callProcess "$OEMStorageControllPath\sas3flash.exe" '-list' + [System.Environment]::NewLine #Invoke-Expression -Command:"cmd.exe /C $OEMStorageControllPath\sas3flash.exe -list"
    $sasinfo1 = Get-CimInstance Win32_PnPSignedDriver | Select-Object devicename, driverversion | Where-Object { $_.devicename -like '*SAS3*' }
    $LsiFWVersion = $($sasinfo -split "`r`n" | Select-String 'NVDATA Version' -CaseSensitive).line.Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
    $LsiBIOSVersion = $($sasinfo -split "`r`n" | Select-String 'BIOS' -CaseSensitive).line.Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
    $LsiUEFIVersion = $($sasinfo -split "`r`n" | Select-String 'UEFI' -CaseSensitive).line.Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
    if ($sasinfo1.count) {
        
        for ($i = 0; $i -lt $sasinfo1.count; $i++) {
            #LSI Firmware
            $sasinfo += callProcess "$OEMStorageControllPath\sas3flash.exe" " -c $i -f $FwPath" + [System.Environment]::NewLine #Invoke-Expression -Command:"cmd.exe /C $OEMStorageControllPath\sas3flash.exe -f $FwPath" -Verbose
            Sleep 5
            #LSI Legacy Mode BIOS
            $sasinfo += callProcess "$OEMStorageControllPath\sas3flash.exe" " -c $i -b $Mptsas" + [System.Environment]::NewLine #Invoke-Expression -Command:"cmd.exe /C $OEMStorageControllPath\sas3flash.exe -b $Mptsas" -Verbose
            Sleep 5
            #LSI UEFI Mode BIOS
            $sasinfo += callProcess "$OEMStorageControllPath\sas3flash.exe" " -c $i -b $Mptx64" + [System.Environment]::NewLine #Invoke-Expression -Command:"cmd.exe /C $OEMStorageControllPath\sas3flash.exe -b $Mptx64" -Verbose
        }
    }
    else {
        #LSI Firmware
        $sasinfo += callProcess "$OEMStorageControllPath\sas3flash.exe" " -f $FwPath" + [System.Environment]::NewLine
        Sleep 5
        #LSI Legacy Mode BIOS
        $sasinfo += callProcess "$OEMStorageControllPath\sas3flash.exe" " -b $Mptsas" + [System.Environment]::NewLine
        Sleep 5
        #LSI UEFI Mode BIOS
        $sasinfo += callProcess "$OEMStorageControllPath\sas3flash.exe" " -b $Mptx64" + [System.Environment]::NewLine
    }
    $sasinfoNew = callProcess "$OEMStorageControllPath\sas3flash.exe" '-list' #Invoke-Expression -Command:"cmd.exe /C $OEMStorageControllPath\sas3flash.exe -list"
    $LsiFWVersionNew = $($sasinfoNew -split "`r`n" | Select-String 'NVDATA Version' -CaseSensitive).line.Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
    $LsiBIOSVersionNew = $($sasinfoNew -split "`r`n" | Select-String 'BIOS' -CaseSensitive).line.Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
    $LsiUEFIVersionNew = $($sasinfoNew -split "`r`n" | Select-String 'UEFI' -CaseSensitive).line.Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()

    addRowFW 'BIOS' $LsiBIOSVersion $LsiBIOSVersionNew
    addRowFW 'UEFI' $LsiUEFIVersion $LsiUEFIVersionNew
    addRowFW 'FW' $LsiFWVersion $LsiFWVersionNew

    $script:tableFW
    $script:rebootneed = 1
}

function Update-OEMFirmware-Mellanox {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$FwPath = ''
    )
    # CheckVersion

    $script:tableFW = @()
    function addRowFW($FWType, $FWVersion, $FWVersionNew) {
        $row2 = New-Object -TypeName PSObject
        $row2 | Add-Member -Name 'Type' -MemberType Noteproperty  -Value $FWType
        $row2 | Add-Member -Name 'FWVersion' -MemberType Noteproperty  -Value $FWVersion
        $row2 | Add-Member -Name 'FWVersionNew' -MemberType Noteproperty  -Value $FWVersionNew

        $script:tableFW += $row2
    }

    #Mellanox Firmware Update
    $OEMMellanoxPath = $PSScriptRoot
    
    $nicinfo = callProcess "$OEMMellanoxPath\mlxup.exe" ' -query --sfx-extract-dir C:\Windows\Temp'
    # $MellanoxFWName = $($nicinfo -split "`r`n" | Select-String 'Device Type').line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[2]
    $MellanoxFWVersion = $($nicinfo -split "`r`n" | Select-String 'FW' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]
    $MellanoxCLPVersion = $($nicinfo -split "`r`n" | Select-String 'CLP' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]
    $MellanoxPXEVersion = $($nicinfo -split "`r`n" | Select-String 'PXE' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]
    $MellanoxUEFIVersion = $($nicinfo -split "`r`n" | Select-String 'UEFI' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]

    # $MellanoxFWPSID = $($nicinfo -split "`r`n" | Select-String 'PSID' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]
    # $MellanoxPDName = $($nicinfo -split "`r`n" | Select-String 'PCI Device Name' -CaseSensitive).line.Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
    
    if ($($nicinfo | Select-String 'ConnectX3Pro').length) {
        $result = callProcess "$OEMMellanoxPath\mlxup.exe" " -i $FwPath --sfx-extract-dir C:\Windows\Temp -u -f -y"
        if ($($result | Select-String '100%Done').length) {
            $resultNew = callProcess "$OEMMellanoxPath\mlxup.exe"
            $MellanoxFWVersionNew = $($resultNew -split "`r`n" | Select-String 'FW' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]
            $MellanoxCLPVersionNew = $($resultNew -split "`r`n" | Select-String 'CLP' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]
            $MellanoxPXEVersionNew = $($resultNew -split "`r`n" | Select-String 'PXE' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]
            $MellanoxUEFIVersionNew = $($resultNew -split "`r`n" | Select-String 'UEFI' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]

            addRowFW 'CLP' $MellanoxCLPVersion $MellanoxCLPVersionNew
            addRowFW 'PXE' $MellanoxPXEVersion $MellanoxPXEVersionNew
            addRowFW 'UEFI' $MellanoxUEFIVersion $MellanoxUEFIVersionNew
            addRowFW 'FW' $MellanoxFWVersion $MellanoxFWVersionNew
            
            $script:tableFW
        }
        $script:rebootneed = 1
    } 
    elseif ($($nicinfo | Select-String 'ConnectX4').length) {
        $result = callProcess "$OEMMellanoxPath\mlxup.exe" " -i $FwPath --sfx-extract-dir C:\Windows\Temp -u -f -y"
        if ($($result | Select-String '100%Done').length) {
            Reset-OEMFirmware-Mellanox -Verbose
            $resultNew = callProcess "$OEMMellanoxPath\mlxup.exe"
            $MellanoxFWVersionNew = $($resultNew -split "`r`n" | Select-String 'FW' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]
            $MellanoxCLPVersionNew = $($resultNew -split "`r`n" | Select-String 'CLP' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]
            $MellanoxPXEVersionNew = $($resultNew -split "`r`n" | Select-String 'PXE' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]
            $MellanoxUEFIVersionNew = $($resultNew -split "`r`n" | Select-String 'UEFI' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]

            addRowFW 'CLP' $MellanoxCLPVersion $MellanoxCLPVersionNew
            addRowFW 'PXE' $MellanoxPXEVersion $MellanoxPXEVersionNew
            addRowFW 'UEFI' $MellanoxUEFIVersion $MellanoxUEFIVersionNew
            addRowFW 'FW' $MellanoxFWVersion $MellanoxFWVersionNew
            
            $script:tableFW
        }
        $script:rebootneed = 1
    }
}

function Reset-OEMFirmware-Mellanox {
    # CheckVersion

    function addRowFW($FWType, $FWVersion, $FWVersionNew) {
        $row2 = New-Object -TypeName PSObject
        $row2 | Add-Member -Name 'Type' -MemberType Noteproperty  -Value $FWType
        $row2 | Add-Member -Name 'FWVersion' -MemberType Noteproperty  -Value $FWVersion
        $row2 | Add-Member -Name 'FWVersionNew' -MemberType Noteproperty  -Value $FWVersionNew

        $script:tableFW += $row2
    }
    #Mellanox Firmware Update
    $OEMMellanoxPath = $PSScriptRoot
    
    $nicinfo = callProcess "$OEMMellanoxPath\mlxup.exe" ' -query --sfx-extract-dir C:\Windows\Temp'
    # $MellanoxFWName = $($nicinfo -split "`r`n" | Select-String 'Device Type').line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[2]
    $MellanoxFWVersion = $($nicinfo -split "`r`n" | Select-String 'FW' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]
    # $MellanoxFWPSID = $($nicinfo -split "`r`n" | Select-String 'PSID' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]
    # $MellanoxPDName = $($nicinfo -split "`r`n" | Select-String 'PCI Device Name' -CaseSensitive).line.Split(':', [System.StringSplitOptions]::RemoveEmptyEntries)[1].Trim()
    # $nicinfo
    if ($($nicinfo | Select-String 'ConnectX4').length) {
        $result = callProcessWorkingDirectory "$OEMMellanoxPath\mlxfwreset.exe" ' -d mt4117_pciconf0 -l 3 reset -y'

        $resultNew = callProcess "$OEMMellanoxPath\mlxup.exe"
        $MellanoxFWVersionNew = $($resultNew -split "`r`n" | Select-String 'FW' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]
        $MellanoxCLPVersionNew = $($resultNew -split "`r`n" | Select-String 'CLP' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]
        $MellanoxPXEVersionNew = $($resultNew -split "`r`n" | Select-String 'PXE' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]
        $MellanoxUEFIVersionNew = $($resultNew -split "`r`n" | Select-String 'UEFI' -CaseSensitive).line.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)[1]

        addRowFW 'CLP' $MellanoxCLPVersion $MellanoxCLPVersionNew
        addRowFW 'PXE' $MellanoxPXEVersion $MellanoxPXEVersionNew
        addRowFW 'UEFI' $MellanoxUEFIVersion $MellanoxUEFIVersionNew
        addRowFW 'FW' $MellanoxFWVersion $MellanoxFWVersionNew
            
        $script:tableFW
        $script:rebootneed = 1
    }
}

function Query-OEMFirmware-Mellanox {
    # CheckVersion
    #Mellanox Firmware Update
    $OEMMellanoxPath = $PSScriptRoot
    
    $nicinfo = callProcess "$OEMMellanoxPath\mlxup.exe" ' -query --sfx-extract-dir C:\Windows\Temp'
    $nicinfo
}

function Update-OEMFirmware-QLogic {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$FwPath = ''
    )
    # CheckVersion

    $script:tableFW = @()
    function addRowFW($FWType, $FWVersion, $FWVersionNew) {
        $row2 = New-Object -TypeName PSObject
        $row2 | Add-Member -Name 'Type' -MemberType Noteproperty  -Value $FWType
        $row2 | Add-Member -Name 'FWVersion' -MemberType Noteproperty  -Value $FWVersion
        $row2 | Add-Member -Name 'FWVersionNew' -MemberType Noteproperty  -Value $FWVersionNew
        $script:tableFW += $row2
    }

    #QLogic Firmware Update
    $OEMQLogicPath = $PSScriptRoot

    $QlogicFW = callProcessStdin "$PSScriptRoot\winfwnx2.exe" '' @('q')
    $QlogicFW = $QlogicFW -split "`r`n"       
    for ($i = 0; $i -lt $QlogicFW.Length; $i++) {
        if ($QlogicFW[$i].ToUpper().IndexOf('QLOGIC') -ge 0) {
            if ($QlogicFW[$i].IndexOf(']') -ge 0) {
                # $QlogicFWName = $($QlogicFW[$i].Split(']')[1].trim() -replace '\s+ ', '_').tostring().split('_')[0]
                $QlogicFWVersion = $($QlogicFW[$i].Split(']')[1].trim() -replace '\s+ ', '_').tostring().split('_')[1]
                # $QlogicFWVersion
            }
        }
    }
    
    $nicinfo = callProcessStdin "$OEMQLogicPath\winfwnx2.exe" '' @('q')
    $FwPath2 = $FwPath.Replace('\', '/')
    $nicinfo = callProcess "$OEMQLogicPath\winfwnx2.exe" " -all upgrade -f -mbi $FwPath2 qlgcrestore"
    
    $QlogicFW = callProcessStdin "$PSScriptRoot\winfwnx2.exe" '' @('q')
    $QlogicFW = $QlogicFW -split "`r`n"       
    for ($i = 0; $i -lt $QlogicFW.Length; $i++) {
        if ($QlogicFW[$i].ToUpper().IndexOf('QLOGIC') -ge 0) {
            if ($QlogicFW[$i].IndexOf(']') -ge 0) {
                # $QlogicFWNameNew = $($QlogicFW[$i].Split(']')[1].trim() -replace '\s+ ', '_').tostring().split('_')[0]
                $QlogicFWVersionNew = $($QlogicFW[$i].Split(']')[1].trim() -replace '\s+ ', '_').tostring().split('_')[1]
                # $QlogicFWVersionNew
            }
        }
    }
    addRowFW 'FW' $QlogicFWVersion $QlogicFWVersionNew
    $script:tableFW

    $script:rebootneed = 1
}

function Update-OEMFirmware-Disk {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$FwPath = '',
        [Parameter(Mandatory = $true, Position = 1)]
        [string]$SerialNumber = ''
    )
    # CheckVersion

    $script:tableFW = @()
    function addRowFW($FWType, $FWVersion, $FWVersionNew) {
        $row2 = New-Object -TypeName PSObject
        $row2 | Add-Member -Name 'Type' -MemberType Noteproperty  -Value $FWType
        $row2 | Add-Member -Name 'FWVersion' -MemberType Noteproperty  -Value $FWVersion
        $row2 | Add-Member -Name 'FWVersionNew' -MemberType Noteproperty  -Value $FWVersionNew
        $script:tableFW += $row2
    }

    $pd = Get-PhysicalDisk | Where-Object SerialNumber -eq $SerialNumber
    $SupportsUpdate = ($pd | Get-StorageFirmwareInformation).SupportsUpdate
    if ($SupportsUpdate) {
        $pdFWVersion = (Get-PhysicalDisk -SerialNumber $SerialNumber | Select-Object FirmwareVersion).FirmwareVersion
        $pd | Update-StorageFirmware -ImagePath $FwPath -SlotNumber 0
        # Get-PhysicalDisk | Where-Object SerialNumber -eq $SerialNumber | Get-StorageFirmwareInformation | Select-Object FirmwareVersionInSlot | out-string
        $pdFWVersionNew = (Get-PhysicalDisk -SerialNumber $SerialNumber | Select-Object FirmwareVersion).FirmwareVersion
        addRowFW 'FW' $pdFWVersion $pdFWVersionNew
        $script:tableFW
    } 
    else {
        $SupportsUpdate | Out-String
    }
}

function Get-Driver_Information {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Path = '',
        [Parameter(Mandatory = $false, Position = 1)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet($true, $false, 1, 0)]
        $Recursive = $false
    )
    # CheckVersion

    $Recursive = [System.Convert]::ToBoolean($Recursive)

    $script:DriverList = @()
    function addRow2($File2, $Path2, $Provider2, $Version2, $DriverVersion2, $DriverDate2, $GUID2, $Class2) {
        $row2 = New-Object -TypeName PSObject
        $row2 | Add-Member -Name 'File' -MemberType Noteproperty  -Value $File2
        $row2 | Add-Member -Name 'Path' -MemberType Noteproperty  -Value $Path2
        $row2 | Add-Member -Name 'Provider' -MemberType Noteproperty  -Value $Provider2
        $row2 | Add-Member -Name 'Version' -MemberType Noteproperty  -Value $Version2
        $row2 | Add-Member -Name 'DriverVersion' -MemberType Noteproperty  -Value $DriverVersion2
        $row2 | Add-Member -Name 'DriverDate' -MemberType Noteproperty  -Value $DriverDate2
        $row2 | Add-Member -Name 'GUID' -MemberType Noteproperty  -Value $GUID2
        $row2 | Add-Member -Name 'Class' -MemberType Noteproperty  -Value $Class2

        $script:DriverList += $row2
    }

    $driversCollection = (Get-ChildItem -Path "$Path" -Filter '*.inf' -recurse:$Recursive -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName)
    foreach ($Driver in $driversCollection) {
        if ((Get-Item $Driver) -is [System.IO.DirectoryInfo]) {
            continue
        }
        $GUID = ''
        $Version = ''
        $DriverVersion = ''
        $DriverDate = ''
        $Provider = ''
        $Class = ''
        $content = Get-Content -Path "$Driver"

        $line = ($content  | Select-String 'ClassGuid')
        if ($null -ne $line) {
            $GUID = $line.Line.Split('=')[-1].Split(' ').Split(';')
            $GUID = ([string]$GUID).trim().Replace('{', '').Replace('}', '')
        }

        $line = ($content  | Select-String 'DriverVer')
        if ($null -ne $line) {
            $Version = $line.Line.Split('=')[-1].Split(' ').Split(';')
            $Version = ([string]$Version).trim().Replace(' ', '')
            $DriverVersion = $Version.Split(',')[1].Trim()
            $DriverDate = $Version.Split(',')[0].Trim()
        }

        $line = ($content  | Select-String 'Provider')
        if ($null -ne $line) {
            $Provider = $line[0].Line.Split('=')[-1].Split(' ').Split(';')
            $Provider = ([string]$Provider).trim()
            if ($Provider.IndexOf('%') -ge 0) {
                $line = ($content  | Select-String $Provider)[1]
                if ($null -ne $line) {
                    $Provider = $line.Line.Split('=')[-1].Split(' ').Split(';')
                    $Provider = ([string]$Provider).trim()
                }

                if ($Provider.IndexOf('%') -ge 0) {
                    for ($i = 0; $i -le $content.Length; $i++) {
                        if ($content[$i].IndexOf('[Manufacturer]') -ge 0) {
                            $Provider = $content[$i + 1].Split('=')[-1].Split(' ').Split(';')
                            $Provider = ([string]$Provider).trim()
                            break
                        }
                    }
                }
            }
        }
        $line = ($content  | Select-String 'Class')
        if ($line.Line -gt 1) { 
            foreach ($l in $Line) {
                if ($l.Line.Split('=')[0].Trim() -eq 'Class') {
                    $line = $l
                    break
                }
            }
        }

        if ($null -ne $line) {
            $Class = $line.Line.Split('=')[-1].Split(' ').Split(';')
            $Class = ([string]$Class).trim().Replace('{', '').Replace('}', '')
        }

        $FileName = Split-Path $Driver -Leaf
        addRow2 $FileName $Driver $Provider $Version $DriverVersion $DriverDate $GUID $Class
    }
    $script:DriverList
}

function Install-Driver {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Path = ''
    )
    # CheckVersion

    $script:updateRecord = @()
    function addRowUpdateRecord($updateState2, $updateAttempted2, $updateSuccessfully2, $updateResult2) {
        $rowUpdateRecord = New-Object -TypeName PSObject
        $rowUpdateRecord | Add-Member -Name 'updateState' -MemberType Noteproperty  -Value $updateState2
        $rowUpdateRecord | Add-Member -Name 'updateAttempted' -MemberType Noteproperty  -Value $updateAttempted2
        $rowUpdateRecord | Add-Member -Name 'updateSuccessfully' -MemberType Noteproperty  -Value $updateSuccessfully2
        $rowUpdateRecord | Add-Member -Name 'updateResult' -MemberType Noteproperty  -Value $updateResult2
        $script:updateRecord += $rowUpdateRecord
    }

    $updateState = ''
    $updateAttempted = ''
    $updateSuccessfully = ''
    $updateResult = ''

    $updateResult = pnputil.exe -i -a $Path

    foreach ($line in $updateResult) {
        if ($line.indexOf('Failed to install the driver') -ge 0) {
            $updateState = 'failed'
        }

        if ($line.indexOf('Driver package added successfully') -ge 0) {
            $updateState = 'success'
        }
        
        if ($line.indexOf('Total attempted') -ge 0) {
            $updateAttempted = $line.Split(':')[1].trim()
        }
        
        if ($line.indexOf('Number successfully imported') -ge 0) {
            $updateSuccessfully = $line.Split(':')[1].trim()
        }
    }

    addRowUpdateRecord $updateState $updateAttempted $updateSuccessfully $updateResult

    $script:updateRecord
}

function CheckVersionOnline() {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$filePath = ''
    )

    New-Item -ItemType File -Path $filePath > $null
    $script:currentVersion = (Get-InstalledModule -Name QCT-Update-Management -ErrorAction SilentlyContinue).version;
    $script:latestVersion = (Find-Module -Name QCT-Update-Management -ErrorAction SilentlyContinue).version;
    $script:currentVersion | Out-File $filePath -Append
    $script:latestVersion | Out-File $filePath -Append
}

function CheckVersion() {
    if ($null -ne $script:currentVersion) {
        $tempPath = [System.IO.Path]::GetTempPath()
        $name = 'QCT-Update-Management.ver'
        $filePath = Join-Path $tempPath $name
        if (Test-Path $filePath) {
            $lastWritetime = $(Get-Item $filePath).LastWriteTime
            if ($lastWritetime.Date -ge $(Get-Date).Date) {
                $content = Get-Content $filePath
                $script:currentVersion = $content[0]
                $script:latestVersion = $content[1]
            } 
            else {
                Remove-Item $filePath
                CheckVersionOnline $filePath
            }
        } 
        else {
            CheckVersionOnline $filePath
        }
    }

    if ($script:currentVersion -ne $script:latestVersion) {
        Write-Warning "Your package version is $script:currentVersion, the latest version is $script:latestVersion.(QCT-Update-Management)"
    }
}

$global:SearchResult = $null
$global:DownloadUpdate = 0
function Check-WindowsUpdate() {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $false)]
        [switch]$OptionalInstallation
    )

    $global:SearchResult = $null
    if ($OptionalInstallation) {
        $OptionalInstallationStr = "IsInstalled=0 and DeploymentAction='OptionalInstallation' and AutoSelectOnWebSites=1 or "
    } else {
        $OptionalInstallationStr = ''
    }
    $Searcher = New-Object -ComObject Microsoft.Update.Searcher
    # https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdatesearcher-search
    $SearchCriteriaAllUpdates = "IsInstalled=0 and DeploymentAction='Installation' and BrowseOnly=0 or
                        $OptionalInstallationStr
                        IsPresent=1 and DeploymentAction='Uninstallation' or
                        IsInstalled=1 and DeploymentAction='Installation' and RebootRequired=1 or
                        IsInstalled=0 and DeploymentAction='Uninstallation' and RebootRequired=1"

    $global:SearchResult = $Searcher.Search($SearchCriteriaAllUpdates).Updates
    $global:SearchResult | Format-Table 
    if ($global:SearchResult.Count -eq 0) {
        Write-Warning 'There are currently no updates available.'
    }
}

function Download-WindowsUpdate() {
    if ($null -eq $global:SearchResult) {
        Check-WindowsUpdate
    } elseif ($global:SearchResult.Count -eq 0) {
        Write-Warning 'There are currently no updates available.'
    }
    $global:DownloadUpdate = 0
    if (($null -ne $global:SearchResult) -and ($global:SearchResult.Count -gt 0)) {
        $Session = New-Object -ComObject Microsoft.Update.Session
        $Downloader = $Session.CreateUpdateDownloader()
        $Downloader.Updates = $global:SearchResult
        $Downloader.Download()
        $global:DownloadUpdate = $global:SearchResult.Count
    }
}

function Install-WindowsUpdate() {
    if ($global:DownloadUpdate -eq 0) {
        Download-WindowsUpdate
    }

    if ($global:DownloadUpdate -ne 0) {
        $Installer = New-Object -ComObject Microsoft.Update.Installer
        $Installer.Updates = $global:SearchResult
        $Result = $Installer.Install()
        $Result
    }
}

function Get-WindowsUpdateInstalledList() {
    $script:WindowsUpdates = @()
    function addRowUWindowsUpdate($HotfixID2, $InstalledDate2, $PS2) {
        $rowWindowsUpdate = New-Object -TypeName PSObject
        $rowWindowsUpdate | Add-Member -Name 'HotfixID' -MemberType Noteproperty  -Value $HotfixID2
        $rowWindowsUpdate | Add-Member -Name 'InstalledDate' -MemberType Noteproperty  -Value $InstalledDate2
        $rowWindowsUpdate | Add-Member -Name 'PS' -MemberType Noteproperty  -Value $PS2
        $script:WindowsUpdates += $rowWindowsUpdate
    }

    $Session = New-Object -ComObject 'Microsoft.Update.Session'
    $Searcher = $Session.CreateUpdateSearcher()
    $historyCount = $Searcher.GetTotalHistoryCount()
    $Updates = $Searcher.QueryHistory(0, $historyCount) | Select-Object Title, Description, Date, @{Name = 'Operation'; Expression = { switch ($_.operation) { 1 { 'Installation' }; 2 { 'Uninstallation' }; 3 { 'Other' } } } }, @{name = 'HotfixID'; expression = { [regex]::match($_.Title, '[a-zA-Z]{2}\d{6,7}').value } } | Sort-Object date -Descending
    $Updates = $Updates | Where-Object { ('KB2267602', 'KB890830', 'KB4052623') -NotContains $_.HotfixID }
    # KB2267602 Security Intelligence Update for Microsoft Defender Antivirus
    # KB890830 Windows Malicious Software Removal Tool x64
    # KB4052623 Update for Windows Defender Antivirus antimalware platform

    foreach ($WindowsUpdate in $Updates) {
        addRowUWindowsUpdate $WindowsUpdate.HotfixID $WindowsUpdate.Date $WindowsUpdate.Title
    }

    $Updates = Get-CimInstance -Class 'win32_quickfixengineering' | Select-Object -Property 'Caption', 'Description', 'HotfixID', @{Name = 'InstalledOn'; Expression = { ([DateTime]($_.InstalledOn)).ToLocalTime() } } | Sort-Object InstalledOn -Descending
    foreach ($WindowsUpdate in $Updates) {
        $updateCatalog = Invoke-WebRequest -Uri $('https://www.catalog.update.microsoft.com/Search.aspx?q=' + $WindowsUpdate.HotfixID) -UseBasicParsing
        $startIndex = $updateCatalog.Content.IndexOf("<table class=""resultsBorder resultsBackGround""")
        if ($startIndex -gt 0) {
            $endIndex = $updateCatalog.Content.IndexOf('</table>', $startIndex)
            $updateCatalogTable = $updateCatalog.Content.Substring($startIndex, $endIndex - $startIndex + 8)

            $nl = [Environment]::NewLine
            $updateCatalogines = $updateCatalogTable.Split($nl, [System.StringSplitOptions]::RemoveEmptyEntries)
            $updateCatalogines = $updateCatalogines | Where-Object { [regex]::match($_, 'KB\d{6,7}').Success } | ForEach-Object { $_.Trim() } | Select-Object -First 1 | Out-String

            if ($updateCatalogines.IndexOf('for Windows') -ge 0) {
                $updateCatalogines = $updateCatalogines.Substring(0, $updateCatalogines.IndexOf('for Windows'))
            } 
            else {
                $updateCatalogines = $WindowsUpdate.Description
            }
        }
        else {
            $updateCatalogines = $WindowsUpdate.Description
        }
        
        addRowUWindowsUpdate $WindowsUpdate.HotfixID $WindowsUpdate.InstalledOn $updateCatalogines
    }

    $script:WindowsUpdates | Sort-Object InstalledDate -Descending
}

function Get-OSVersion() {
    $CurrentVersion = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
    $CurrentVersion.ProductName + ' ' + $CurrentVersion.DisplayVersion + '(' + $CurrentVersion.CurrentBuild + '.' + $CurrentVersion.UBR + ')'
}

function Get-ServerPhysicalDisk() {
    $SDDCCimInstance =  Get-CimInstance -Namespace  'root/sddc/management' -ClassName 'SDDC_Drive'
    $phds = Get-PhysicalDisk | Select-Object *,@{L='Server';E={$SerialNumber=$_.AdapterSerialNumber;if ($SerialNumber -eq $null){$SerialNumber=$_.SerialNumber};($SDDCCimInstance| Where-Object SerialNumber -eq $SerialNumber).Server}} | Sort-Object size,server

    $defaultDisplaySet = 'DeviceId','FriendlyName','SerialNumber','MediaType','CanPool','OperationalStatus','HealthStatus','Usage','Size','Server'
    $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',[string[]]$defaultDisplaySet)
    $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
    $phds | Add-Member -MemberType MemberSet -Name PSStandardMembers $PSStandardMembers
    $phds
}

function Get-SecuredCore() {
    try {
      $ErrorActionPreference = 'SilentlyContinue'
      $InformationPreference = 'SilentlyContinue'
      $WarningPreference = 'SilentlyContinue'
      $ProgressPreference = 'SilentlyContinue'

      Import-Module CimCmdlets
      Import-Module TrustedPlatformModule
  
      function CheckTpmVersion {
        $TpmObj = Get-CimInstance -classname Win32_Tpm -namespace root\cimv2\Security\MicrosoftTpm
        if ($null -ne $TpmObj) {
          return $TpmObj.SpecVersion[0] -eq '2'
        }
        return $false
      }

      function checkSecureBoot {
        if ($null -ne (Get-Command Confirm-SecureBootUEFI -ErrorAction SilentlyContinue)) {
          try {
            return Confirm-SecureBootUEFI
          }
          catch {
            return $false
          }
        }
        return $false
      }

      function CheckDGSecurityServicesRunning($_val) {
        $DGObj = Get-CimInstance -classname Win32_DeviceGuard -namespace root\Microsoft\Windows\DeviceGuard
      
        for ($i = 0; $i -lt $DGObj.SecurityServicesRunning.length; $i++) {
          if ($DGObj.SecurityServicesRunning[$i] -eq $_val) {
            return $true
          }
        }
        return $false
      }

      function CheckDGSecurityServicesConfigured($_val) {
        $DGObj = Get-CimInstance -classname Win32_DeviceGuard -namespace root\Microsoft\Windows\DeviceGuard
        if ($_val -in $DGObj.SecurityServicesConfigured) {
          return $true
        }
        return $false
      }

      function CheckVBS {
        return (Get-CimInstance -classname Win32_DeviceGuard -namespace root\Microsoft\Windows\DeviceGuard).VirtualizationBasedSecurityStatus
      }

      function FeatureResult($FeatureName, $configured, $status) {
        if ($configured -and $status) {
            $Result = 'On'
        } elseif ((-not $configured) -and $status) {
            if (($FeatureName -eq 'secureBoot') -or ($FeatureName -eq 'tpm20') -or ($FeatureName -eq 'bootDMAProtection')) {
                $Result = 'On'
            } else {
                $Result = 'Running but not configured'
            }
        } elseif ($configured -and (-not $status)) {
            $Result = 'Enabled but not running'
        } else {
            if (($FeatureName -eq 'hvci') -or ($FeatureName -eq 'secureBoot') -or ($FeatureName -eq 'systemGuard')) {
                $Result = 'Not configured'
            } else {
                $Result = 'Not supported'
            }
        }
        return $Result
      }

      $bootDMAProtectionCheck =
@"
namespace SystemInfo
    {
    using System;
    using System.Runtime.InteropServices;
 
    public static class NativeMethods
    {
        internal enum SYSTEM_DMA_GUARD_POLICY_INFORMATION : int
        {
            // </summary>
            SystemDmaGuardPolicyInformation = 202
        }
 
        [DllImport("ntdll.dll")]
        internal static extern Int32 NtQuerySystemInformation(
        SYSTEM_DMA_GUARD_POLICY_INFORMATION SystemDmaGuardPolicyInformation,
        IntPtr SystemInformation,
        Int32 SystemInformationLength,
        out Int32 ReturnLength);
 
        public static byte BootDmaCheck() {
        Int32 result;
        Int32 SystemInformationLength = 1;
        IntPtr SystemInformation = Marshal.AllocHGlobal(SystemInformationLength);
        Int32 ReturnLength;
 
        result = NativeMethods.NtQuerySystemInformation(
                    NativeMethods.SYSTEM_DMA_GUARD_POLICY_INFORMATION.SystemDmaGuardPolicyInformation,
                    SystemInformation,
                    SystemInformationLength,
                    out ReturnLength);
 
        if (result == 0) {
            byte info = Marshal.ReadByte(SystemInformation, 0);
            return info;
        }
 
        return 0;
        }
    }
    }
"@

      Add-Type -TypeDefinition $bootDMAProtectionCheck

      $feature = 'tpm20'
      $status = CheckTpmVersion
      $description = FeatureResult $feature $null $status
      $TPM20Obj = @{'Status' = $status; 'Configured' = $null; 'Description' = $description }

      $feature = 'secureBoot'
      $status = checkSecureBoot
      $description = FeatureResult $feature $null $status
      $secureBoot = @{'Status' = $status; 'Configured' = $null; 'Description' = $description }

      $feature = 'bootDMAProtection'
      $status = ([SystemInfo.NativeMethods]::BootDmaCheck()) -ne 0
      $description = FeatureResult $feature $null $status
      $bootDMAProtection = @{'Status' = $status; 'Configured' = $null; 'Description' = $description }

      $feature = 'vbs'
      $vbsStatus = [int](CheckVBS)
      $vbsRunning = if ($vbsStatus -eq 2) { $true } else { $false }
      $vbsConfigured = if ($vbsStatus -gt 0) { $true } else { $false } 
      $description = FeatureResult $feature $vbsConfigured $vbsRunning
      $VBS = @{'Status' = $vbsRunning; 'Configured' = $vbsConfigured; 'Description' = $description }

      $feature = 'hvci'
      $status = CheckDGSecurityServicesRunning(2)
      $configured = CheckDGSecurityServicesConfigured(2)
      $description = FeatureResult $feature $configured $status
      $HVCI = @{'Status' = $status; 'Configured' = $configured; 'Description' = $description }
      
      $feature = 'systemGuard'
      $status = CheckDGSecurityServicesRunning(3)
      $configured = CheckDGSecurityServicesConfigured(3)
      $description = FeatureResult $feature $configured $status
      $systemGuard = @{'Status' = $status; 'Configured' = $configured; 'Description' = $description }

      $securedCoreFeatures = @{}
      $securedCoreFeatures.Add('tpm20', $TPM20Obj)
      $securedCoreFeatures.Add('secureBoot', $secureBoot)
      $securedCoreFeatures.Add('bootDMAProtection', $bootDMAProtection)
      $securedCoreFeatures.Add('vbs', $VBS)
      $securedCoreFeatures.Add('hvci', $HVCI)
      $securedCoreFeatures.Add('systemGuard', $systemGuard)
      $securedCoreFeatures
      # foreach ($key in $securedCoreFeatures.Keys) { $key; $securedCoreFeatures.$key | Format-Table ; }
    }
    catch {
      Write-Host $_.Exception.Message
    }
}

function Set-SecuredCore() {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $false, Position = 0)]
        [ValidateSet('hvci', 'credentialGuard', 'systemGuard', 'vbs')]
        [string]$featureName = '',
        [Parameter(Mandatory = $false, Position = 1)]
        [ValidateSet(1, 0)]
        [int]$value,
        [Parameter(Mandatory = $false, Position = 2)]
        [ValidateSet(1, 0)]
        [int]$secureBoot
    )

    try {
      $ErrorActionPreference = 'SilentlyContinue'
      $InformationPreference = 'SilentlyContinue'
      $WarningPreference = 'SilentlyContinue'
      $ProgressPreference = 'SilentlyContinue'

      $Script:secureBoot = [int]$secureBoot
      if (-not($Script:secureBoot -in @(0, 1, 3))) {
        Throw "Invalid value for parameter $Script:secureBoot. Use 0 (Disable RequirePlatformSecurityFeatures), 1 (Secure Boot only) or 3 (Secure Boot and DMA protection)"
      }
  
      $Script:value = [int]$value
      $valueWord = if ($value -eq 0 ) { 'disable' } else { 'enable' };
      if (-not($Script:value -in @(0, 1))) {
        Throw "Invalid value for parameter $Script:value. Use 0 to disable or 1 to enable a secured core feature"
      }

      function checkRemote {
        if ($PSSenderInfo -or (Get-Host).Name -eq 'ServerRemoteHost') {
          return $true
        }
        return $false
      }
      $Script:isRemoteConnection = checkRemote

      function ExecuteCommandAndLog($_cmd) {
        try {
          Invoke-Expression $_cmd | Out-String
        }
        catch {
          Write-Host "Exception while exectuing $_cmd"
          Throw $_.Exception.Message
        }
      }

      function SystemGuard {
        $path = 'Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\SystemGuard'
        if (-Not(Test-Path $path)) {
          New-Item $path -Force
        }
        Set-ItemProperty -path $path -name 'Enabled' -value $Script:value -Type 'DWORD' -Force
      }

      function HVCI {
        $path = 'Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity'
        if (-Not(Test-Path $path)) {
          New-Item $path -Force
        }

        $uefiLock = Get-ItemProperty -Path $path | `
        Microsoft.PowerShell.Utility\Select-Object 'Locked' -ExpandProperty 'Locked' -ErrorAction SilentlyContinue

        if ($uefiLock -eq 1 -and $Script:isRemoteConnection) {
          Throw "UEFI lock enabled. Cannot $Script:actionWord HVCI remotely."
        }

        if ($Script:value -eq 0) {
          Remove-ItemProperty -Path $path -Name 'WasEnabledBy' -ErrorAction SilentlyContinue
        }

        if ($Script:value -eq 1) {
          # Note: VBS will automatically turn on if you enable VBS (HVCI, Cred Guard)
          VBS
          Set-ItemProperty -path $path -name 'WasEnabledBy' -value 0 -Type 'DWORD' -Force
        }

        # Enable HVCI
        Set-ItemProperty -path $path -name 'Enabled' -value $Script:value -Type 'DWORD' -Force
      }

      function VBS {
          $path = 'Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceGuard'
          if (-Not(Test-Path $path)) {
            New-Item $path -Force
          }

          # For Windows 10 version 1607 and later
          $uefiLock = Get-ItemProperty -Path $path | `
          Microsoft.PowerShell.Utility\Select-Object 'Locked' -ExpandProperty 'Locked' -ErrorAction SilentlyContinue

          # Check UEFI lock for Windows 10 version 1511 and earlier
          $uefiUnlock = Get-ItemProperty -Path $path | `
          Microsoft.PowerShell.Utility\Select-Object 'Unocked' -ExpandProperty 'Unocked' -ErrorAction SilentlyContinue

          if (($uefiLock -eq 1 -or $uefiUnlock -eq 0) -and $Script:isRemoteConnection) {
            Throw "UEFI lock enabled. Cannot $valueWord VBS remotely."
          }

          $currentSecureBootValue = Get-ItemProperty -Path $path | `
          Microsoft.PowerShell.Utility\Select-Object 'RequirePlatformSecurityFeatures' -ExpandProperty 'RequirePlatformSecurityFeatures' -ErrorAction SilentlyContinue

          if ($Script:value -eq 0) {
            # Note: all other VBS (HVCI, Cred Guard) need to be disabled as well, or VBS will automatically turn on
            HVCI
            CredentialGuard

            Set-ItemProperty -path $path -Name 'EnableVirtualizationBasedSecurity' -Value 0 -Type 'DWORD' -Force

            # Disable secure boot
            if ($currentSecureBootValue) {
              Remove-ItemProperty -Path $path -Name 'RequirePlatformSecurityFeatures' -ErrorAction SilentlyContinue
            }
          }

          if ($Script:value -eq 1) {
            Set-ItemProperty -path $path -Name 'EnableVirtualizationBasedSecurity' -Value 1 -Type 'DWORD' -Force

            # Enable Secure Boot
            if ($Script:secureBoot -in @(1, 3)) {
              <#
              - 1: Secure Boot only
              - 3: Secure Boot and DMA protection.
              #>

              Set-ItemProperty -path $path -name 'RequirePlatformSecurityFeatures' -value $Script:secureBoot -Type 'DWORD' -Force
            }
          }
      }

      function CredentialGuard {
        $securityServicesConfigured = (Get-CimInstance -classname Win32_DeviceGuard -namespace root\Microsoft\Windows\DeviceGuard).SecurityServicesConfigured
        if ($securityServicesConfigured.Length -gt 0 -and
          ($Script:value -eq 0 -and (-not(1 -in $securityServicesConfigured))) -or
          ($Script:value -eq 1 -and (1 -in $securityServicesConfigured))) {
          return;
        }

        $path = 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\LSA'
        if (-Not(Test-Path $path)) {
          New-Item $path -Force
        }

        <#
          0: (Disabled) Turns off Windows Defender Credential Guard remotely if configured previously without UEFI Lock
          1: (Enabled with UEFI lock) Turns on Windows Defender Credential Guard with UEFI lock
          2: (Enabled without lock) Turns on Windows Defender Credential Guard without UEFI lock
        #>

        $credGuardValue = Get-ItemProperty -Path $path | `
        Microsoft.PowerShell.Utility\Select-Object 'LsaCfgFlags' -ExpandProperty 'LsaCfgFlags' -ErrorAction SilentlyContinue

        if ($credGuardValue -eq 1 -and $Script:isRemoteConnection) {
          Throw "UEFI lock enabled. Cannot $valueWord Credential Guard remotely."
        }

        if ($Script:value -eq 0) {
          Remove-ItemProperty -path $path -name 'LsaCfgFlags' -ErrorAction SilentlyContinue -Force

          # This setting is persisted in EFI (firmware) variables so we need to delete it
          $settingPath = 'Registry::HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\DeviceGuard'
          Remove-ItemProperty -Path $settingPath -Name 'LsaCfgFlags' -ErrorAction SilentlyContinue

          # Set of commands to run SecConfig.efi to delete UEFI variables if were set in pre OS
          $FreeDrive = Get-ChildItem function:[s-z]: -Name | Where-Object { !(Test-Path $_) } | Get-Random
            ExecuteCommandAndLog 'mountvol $FreeDrive /s'
            Copy-Item "$env:windir\System32\SecConfig.efi" $FreeDrive\EFI\Microsoft\Boot\SecConfig.efi -Force | Out-String
            ExecuteCommandAndLog 'bcdedit /create "{0cb3b571-2f2e-4343-a879-d86a476d7215}" /d DGOptOut /application osloader'
            ExecuteCommandAndLog 'bcdedit /set "{0cb3b571-2f2e-4343-a879-d86a476d7215}" path \EFI\Microsoft\Boot\SecConfig.efi'
            ExecuteCommandAndLog 'bcdedit /set "{bootmgr}" bootsequence "{0cb3b571-2f2e-4343-a879-d86a476d7215}"'
            ExecuteCommandAndLog 'bcdedit /set "{0cb3b571-2f2e-4343-a879-d86a476d7215}" loadoptions DISABLE-LSA-ISO,DISABLE-VBS'
            ExecuteCommandAndLog 'bcdedit /set "{0cb3b571-2f2e-4343-a879-d86a476d7215}" device partition=$FreeDrive'
            ExecuteCommandAndLog 'mountvol $FreeDrive /d'
        }
        else {
          VBS
          Set-ItemProperty -path $path -name 'LsaCfgFlags' -value 1 -Type 'DWORD' -Force
        }
      }

      switch ($featureName)
      {
        'hvci' 
        {
          HVCI
        }
        'credentialGuard' 
        {
          CredentialGuard
        }
        'systemGuard' 
        {
          SystemGuard
        }
        'vbs' 
        {
          VBS
        }
      }
    }
    catch {
      Write-Host $_.Exception.Message
    }
}

Export-ModuleMember Get-Driver_FW
Export-ModuleMember Enable-DiskLocate
Export-ModuleMember Disable-DiskLocate
Export-ModuleMember Get-SmartInfo
Export-ModuleMember Get-ClusterDriver_FW
Export-ModuleMember Update-OEMFirmware-LSI
Export-ModuleMember Update-OEMFirmware-Mellanox
Export-ModuleMember Update-OEMFirmware-QLogic
Export-ModuleMember Update-OEMFirmware-Disk
Export-ModuleMember Reset-OEMFirmware-Mellanox
Export-ModuleMember Query-OEMFirmware-Mellanox
Export-ModuleMember Get-Driver_Information
Export-ModuleMember Install-Driver
Export-ModuleMember Check-WindowsUpdate
Export-ModuleMember Download-WindowsUpdate
Export-ModuleMember Install-WindowsUpdate
Export-ModuleMember Get-WindowsUpdateInstalledList
Export-ModuleMember Get-OSVersion
Export-ModuleMember Get-ServerPhysicalDisk
Export-ModuleMember Get-SecuredCore
Export-ModuleMember Set-SecuredCore

# SIG # Begin signature block
# MIIbqAYJKoZIhvcNAQcCoIIbmTCCG5UCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUSW0/4EEtUdlFE9LKRziTm/Ry
# cG2gghYiMIIDFzCCAf+gAwIBAgIQFizPTEXNvrZBigm0vSZdcTANBgkqhkiG9w0B
# AQsFADAXMRUwEwYDVQQDDAx3YWMud2RhYy5xY3QwHhcNMjIwODMxMDE1MjI5WhcN
# MjUwODMxMDIwMjI0WjAXMRUwEwYDVQQDDAx3YWMud2RhYy5xY3QwggEiMA0GCSqG
# SIb3DQEBAQUAA4IBDwAwggEKAoIBAQD1T89HN6W7Jv+5yA+5fp0sukVzVUNeqsvg
# Zu9TSDCK2wlHpvL8LPDSzp0xOSvnXus5YTs9EzclHFMCbE1IJZb5lDw9ffHgumXt
# tXr5gddxtRaVBuZncD1noi8wfXk+Me6I2KGZEA2h5yCeV7fmpBXjp9ya5IkZEACc
# 5Z6oJtUfye+PO0L5AMJGFgPT/5ELGO1wTHda2g4kJWX8IhEcTI+aTTeUKrMmJNX8
# emHdQANoTNmhNglm60i18py6ekZsGgORvRz/W/KJdVr63SYJxVzqLNwcr7iZuFKk
# +up7ZsqdhLMly+qrmDDe5s3fn2/YamdyywrYCnv3IY87sapW7bvpAgMBAAGjXzBd
# MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAXBgNVHREEEDAO
# ggx3YWMud2RhYy5xY3QwHQYDVR0OBBYEFK12tsDjrwoBxzX4InAacHPyomM8MA0G
# CSqGSIb3DQEBCwUAA4IBAQAeCnXUG5gZayX4SQsnVUw2tcuNh5O4GszbfuIDm13U
# 52WgvpONGrz/NNhsAdcTR0giOGVNqVcUP21Qwa+SkogVSvHcpwoFruwy7DaXnRmO
# r7tE+/URzmtULEEilnEiK8FfUZneat5M/tmIG7kD40HfCdLMUpB9bX4+jBeVZa1o
# EhiErmfC7R3oHxwQfAcR4oZxQqZOOK+qsYh3csF65v0teG95iT0QDnIkZplQViDo
# L1H0UT40qGor5M6EhRCC8Iq/gso29zMEmGEDJkdeaJioEa4tiqcadtvZAGGbcFs2
# gBVNQK/pWvAcTsWEuL1Nc+Q1KqQEQOJ8GiL70S+udRFWMIIFjTCCBHWgAwIBAgIQ
# DpsYjvnQLefv21DiCEAYWjANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQGEwJVUzEV
# MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t
# MSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMjIwODAx
# MDAwMDAwWhcNMzExMTA5MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMM
# RGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQD
# ExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqGSIb3DQEBAQUAA4IC
# DwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEppz1Yq3aa
# za57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllV
# cq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT
# +CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd
# 463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+
# EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/CNdaSaTC5qmgZ92k
# J7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtmmnTK3kse5w5j
# rubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7
# f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJU
# KSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+wh
# X8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQAB
# o4IBOjCCATYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5n
# P+e6mK4cD08wHwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDgYDVR0P
# AQH/BAQDAgGGMHkGCCsGAQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29j
# c3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3J0MEUGA1UdHwQ+MDww
# OqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJ
# RFJvb3RDQS5jcmwwEQYDVR0gBAowCDAGBgRVHSAAMA0GCSqGSIb3DQEBDAUAA4IB
# AQBwoL9DXFXnOF+go3QbPbYW1/e/Vwe9mqyhhyzshV6pGrsi+IcaaVQi7aSId229
# GhT0E0p6Ly23OO/0/4C5+KH38nLeJLxSA8hO0Cre+i1Wz/n096wwepqLsl7Uz9FD
# RJtDIeuWcqFItJnLnU+nBgMTdydE1Od/6Fmo8L8vC6bp8jQ87PcDx4eo0kxAGTVG
# amlUsLihVo7spNU96LHc/RzY9HdaXFSMb++hUD38dglohJ9vytsgjTVgHAIDyyCw
# rFigDkBjxZgiwbJZ9VVrzyerbHbObyMt9H5xaiNrIv8SuFQtJ37YOtnwtoeW/VvR
# XKwYw02fc7cBqZ9Xql4o4rmUMIIGrjCCBJagAwIBAgIQBzY3tyRUfNhHrP0oZipe
# WzANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNl
# cnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdp
# Q2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjIwMzIzMDAwMDAwWhcNMzcwMzIyMjM1
# OTU5WjBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5
# BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0
# YW1waW5nIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxoY1Bkmz
# wT1ySVFVxyUDxPKRN6mXUaHW0oPRnkyibaCwzIP5WvYRoUQVQl+kiPNo+n3znIkL
# f50fng8zH1ATCyZzlm34V6gCff1DtITaEfFzsbPuK4CEiiIY3+vaPcQXf6sZKz5C
# 3GeO6lE98NZW1OcoLevTsbV15x8GZY2UKdPZ7Gnf2ZCHRgB720RBidx8ald68Dd5
# n12sy+iEZLRS8nZH92GDGd1ftFQLIWhuNyG7QKxfst5Kfc71ORJn7w6lY2zkpsUd
# zTYNXNXmG6jBZHRAp8ByxbpOH7G1WE15/tePc5OsLDnipUjW8LAxE6lXKZYnLvWH
# po9OdhVVJnCYJn+gGkcgQ+NDY4B7dW4nJZCYOjgRs/b2nuY7W+yB3iIU2YIqx5K/
# oN7jPqJz+ucfWmyU8lKVEStYdEAoq3NDzt9KoRxrOMUp88qqlnNCaJ+2RrOdOqPV
# A+C/8KI8ykLcGEh/FDTP0kyr75s9/g64ZCr6dSgkQe1CvwWcZklSUPRR8zZJTYsg
# 0ixXNXkrqPNFYLwjjVj33GHek/45wPmyMKVM1+mYSlg+0wOI/rOP015LdhJRk8mM
# DDtbiiKowSYI+RQQEgN9XyO7ZONj4KbhPvbCdLI/Hgl27KtdRnXiYKNYCQEoAA6E
# VO7O6V3IXjASvUaetdN2udIOa5kM0jO0zbECAwEAAaOCAV0wggFZMBIGA1UdEwEB
# /wQIMAYBAf8CAQAwHQYDVR0OBBYEFLoW2W1NhS9zKXaaL3WMaiCPnshvMB8GA1Ud
# IwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNV
# HSUEDDAKBggrBgEFBQcDCDB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0
# dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2Vy
# dHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0f
# BDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1
# c3RlZFJvb3RHNC5jcmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcB
# MA0GCSqGSIb3DQEBCwUAA4ICAQB9WY7Ak7ZvmKlEIgF+ZtbYIULhsBguEE0TzzBT
# zr8Y+8dQXeJLKftwig2qKWn8acHPHQfpPmDI2AvlXFvXbYf6hCAlNDFnzbYSlm/E
# UExiHQwIgqgWvalWzxVzjQEiJc6VaT9Hd/tydBTX/6tPiix6q4XNQ1/tYLaqT5Fm
# niye4Iqs5f2MvGQmh2ySvZ180HAKfO+ovHVPulr3qRCyXen/KFSJ8NWKcXZl2szw
# cqMj+sAngkSumScbqyQeJsG33irr9p6xeZmBo1aGqwpFyd/EjaDnmPv7pp1yr8TH
# wcFqcdnGE4AJxLafzYeHJLtPo0m5d2aR8XKc6UsCUqc3fpNTrDsdCEkPlM05et3/
# JWOZJyw9P2un8WbDQc1PtkCbISFA0LcTJM3cHXg65J6t5TRxktcma+Q4c6umAU+9
# Pzt4rUyt+8SVe+0KXzM5h0F4ejjpnOHdI/0dKNPH+ejxmF/7K9h+8kaddSweJywm
# 228Vex4Ziza4k9Tm8heZWcpw8De/mADfIBZPJ/tgZxahZrrdVcA6KYawmKAr7ZVB
# tzrVFZgxtGIJDwq9gdkT/r+k0fNX2bwE+oLeMt8EifAAzV3C+dAjfwAL5HYCJtnw
# ZXZCpimHCUcr5n8apIUP/JiW9lVUKx+A+sDyDivl1vupL0QVSucTDh3bNzgaoSv2
# 7dZ8/DCCBsAwggSooAMCAQICEAxNaXJLlPo8Kko9KQeAPVowDQYJKoZIhvcNAQEL
# BQAwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYD
# VQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFt
# cGluZyBDQTAeFw0yMjA5MjEwMDAwMDBaFw0zMzExMjEyMzU5NTlaMEYxCzAJBgNV
# BAYTAlVTMREwDwYDVQQKEwhEaWdpQ2VydDEkMCIGA1UEAxMbRGlnaUNlcnQgVGlt
# ZXN0YW1wIDIwMjIgLSAyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
# z+ylJjrGqfJru43BDZrboegUhXQzGias0BxVHh42bbySVQxh9J0Jdz0Vlggva2Sk
# /QaDFteRkjgcMQKW+3KxlzpVrzPsYYrppijbkGNcvYlT4DotjIdCriak5Lt4eLl6
# FuFWxsC6ZFO7KhbnUEi7iGkMiMbxvuAvfTuxylONQIMe58tySSgeTIAehVbnhe3y
# YbyqOgd99qtu5Wbd4lz1L+2N1E2VhGjjgMtqedHSEJFGKes+JvK0jM1MuWbIu6pQ
# OA3ljJRdGVq/9XtAbm8WqJqclUeGhXk+DF5mjBoKJL6cqtKctvdPbnjEKD+jHA9Q
# Bje6CNk1prUe2nhYHTno+EyREJZ+TeHdwq2lfvgtGx/sK0YYoxn2Off1wU9xLokD
# EaJLu5i/+k/kezbvBkTkVf826uV8MefzwlLE5hZ7Wn6lJXPbwGqZIS1j5Vn1TS+Q
# Hye30qsU5Thmh1EIa/tTQznQZPpWz+D0CuYUbWR4u5j9lMNzIfMvwi4g14Gs0/EH
# 1OG92V1LbjGUKYvmQaRllMBY5eUuKZCmt2Fk+tkgbBhRYLqmgQ8JJVPxvzvpqwcO
# agc5YhnJ1oV/E9mNec9ixezhe7nMZxMHmsF47caIyLBuMnnHC1mDjcbu9Sx8e47L
# ZInxscS451NeX1XSfRkpWQNO+l3qRXMchH7XzuLUOncCAwEAAaOCAYswggGHMA4G
# A1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF
# BwMIMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATAfBgNVHSMEGDAW
# gBS6FtltTYUvcyl2mi91jGogj57IbzAdBgNVHQ4EFgQUYore0GH8jzEU7ZcLzT0q
# lBTfUpwwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2NybDMuZGlnaWNlcnQuY29t
# L0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNy
# bDCBkAYIKwYBBQUHAQEEgYMwgYAwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
# Z2ljZXJ0LmNvbTBYBggrBgEFBQcwAoZMaHR0cDovL2NhY2VydHMuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNIQTI1NlRpbWVTdGFtcGluZ0NB
# LmNydDANBgkqhkiG9w0BAQsFAAOCAgEAVaoqGvNG83hXNzD8deNP1oUj8fz5lTmb
# Jeb3coqYw3fUZPwV+zbCSVEseIhjVQlGOQD8adTKmyn7oz/AyQCbEx2wmIncePLN
# fIXNU52vYuJhZqMUKkWHSphCK1D8G7WeCDAJ+uQt1wmJefkJ5ojOfRu4aqKbwVNg
# CeijuJ3XrR8cuOyYQfD2DoD75P/fnRCn6wC6X0qPGjpStOq/CUkVNTZZmg9U0rIb
# f35eCa12VIp0bcrSBWcrduv/mLImlTgZiEQU5QpZomvnIj5EIdI/HMCb7XxIstiS
# DJFPPGaUr10CU+ue4p7k0x+GAWScAMLpWnR1DT3heYi/HAGXyRkjgNc2Wl+WFrFj
# DMZGQDvOXTXUWT5Dmhiuw8nLw/ubE19qtcfg8wXDWd8nYiveQclTuf80EGf2JjKY
# e/5cQpSBlIKdrAqLxksVStOYkEVgM4DgI974A6T2RUflzrgDQkfoQTZxd639ouiX
# dE4u2h4djFrIHprVwvDGIqhPm73YHJpRxC+a9l+nJ5e6li6FV8Bg53hWf2rvwpWa
# SxECyIKcyRoFfLpxtU56mWz06J7UWpjIn7+NuxhcQ/XQKujiYu54BNu90ftbCqhw
# fvCXhHjjCANdRyxjqCU4lwHSPzra5eX25pvcfizM/xdMTQCi2NYBDriL7ubgclWJ
# LCcZYfZ3AYwxggTwMIIE7AIBATArMBcxFTATBgNVBAMMDHdhYy53ZGFjLnFjdAIQ
# FizPTEXNvrZBigm0vSZdcTAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAig
# AoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgEL
# MQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUCltd7TcJMjLNEDddsOCm
# U4wGXoswDQYJKoZIhvcNAQEBBQAEggEAO0wRk9czVIu0GG/oJJ0YhHRDyJ4oWP7t
# X+F1Zwxp8drkLuCuZpZKRghlAPdPI0bkQmje+2HI2tBIypWuiuKPfwYqod+AYoxx
# 9/NXZeQ69iH1oqhdGeW6iHaiUw9D4R3FKFKrloafZhvUAF8v+5TrRUQOFGAMsX39
# NMnm+Y9xhvI22uCSOOkx8ZxtNDfDXTLrWYdRWse+T0nKLWDvF90kfsuTLxPJxt+d
# VyOrUgHPG14OJbTtjB5urYk9n6K2D06mR/AiIbSVEgrgSwTyybs3vtLS25Jqb14L
# GNmSp33aQt5n3GQX+sREqusUCbDSQZOvEazi7xfvlyO6tys7BbUWIKGCAyAwggMc
# BgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQK
# Ew5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBS
# U0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAxNaXJLlPo8Kko9KQeAPVow
# DQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqG
# SIb3DQEJBTEPFw0yMzAzMDIwMjMwMThaMC8GCSqGSIb3DQEJBDEiBCBLvZdz4+fs
# flplx2iW3g9wgSe3/NnwrwYloX5jM+nLnjANBgkqhkiG9w0BAQEFAASCAgCcELeT
# pXBocpYSujsKWf4rbfM/1AId9IjAGxo75V1CQ5MHH/m0V0WvHG0i4xl/WN2qHjz5
# 4zWmfZvJgNeZgCH5y1nzmsLgmx//4VLh3iCpYYCzgU5HVBY+fxZg+mYth8PnKpZI
# KLTn2WQwg2kK72eUCb4xq2Wsh5dVZmEfqB6nqrpwnTgTm+1f7ZxM0v3fmnfICWfT
# WY7bgoolO3WtzfcNL7XSh1mRPz85atHnyJ9Rl0YO/X/cFMiSEzbtTZgk10/atEjF
# nhVA4C6Y8HO3r0kp/aHnTJa4zuGxLlz9EW1Wh1D/KdbNyYQAazadyNMX4XNTVi0b
# Oi3B3hyJBiB4f6KeDJ1ZHoDww+gsZaZoe/6tbb40ed2brTfIzU7fCYZEHYI5k3U9
# Duoma5JmSY2QZYDgg3g7M1Fugj50cjSoK0nKfo5Tni6yGhF7rcr/9eiWUa58IWBQ
# whJBcFkLMzwwuT0UliDGE8ENClRw62kORVUa5KP9Xnk6vFZuyIgGenZWYN9sfpf3
# Xfu+Qm3bU4z6ZTbzmM9p2qOCDooQzqsD7HCdVETODE0LtcxbD0YKIXoXrywiGc8C
# rP33LF+6M4BNR8KOjHnD09KH8g0usr7Aeb5gclD8Et/TtU0eMvXldJsxzrU9OFCd
# 8no79hrHheZvpJDpxDqWTLBHCgkXm0sDQ6ZuFg==
# SIG # End signature block