UninstallFastLinqPowerKit.ps1

# Script level parameters
Param (
    [Switch] $UninstallRESTServerOnly = $false
)

# Functions

Function Test-IfNano {
    # Check if OS is Nano or Non-Nano
    $envInfo = [Environment]::OSVersion.Version
    $envInfoCimInst = Get-CimInstance Win32_OperatingSystem
    return ( ($envInfo.Major -eq 10) -and ($envInfo.Minor -eq 0) -and ($envInfoCimInst.ProductType -eq 3) -and ($envInfoCimInst.SuiteMask -eq 272) )
}

Function Test-IfServerCore {
    # Check if OS is Server Core
    $regKey = 'hklm:/software/microsoft/windows nt/currentversion'
    return (Get-ItemProperty $regKey).InstallationType -eq 'Server Core'
}

Function Test-RegistryValue {
    Param (
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Path,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Value
    )

    try {
        Get-ItemProperty -Path $Path | Select-Object -ExpandProperty $Value -ErrorAction Stop 2>&1 | Out-Null
        return $true
    } catch {
        return $false
    }
}

Function Get-AppxPackageWrapper {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $AppxName )

    $numAttempts = 3
    $numSecondsBetweenAttempts = 5
    for ($i=0; $i -lt $numAttempts; $i++) {
        $appxPkg = Get-AppxPackage | Where-Object { $_.name -eq $AppxName }
        if ($appxPkg -ne $null) { break }
        Write-Host "Couldn't find Appx package. Trying again in $numSecondsBetweenAttempts seconds (attempt $($i+1) of $numAttempts) ..." -ForegroundColor DarkRed
        Start-Sleep -Seconds $numSecondsBetweenAttempts
    }
    if ($appxPkg -eq $null) {
        Write-Host 'Failed to find Appx package. Please try running this script again.' -ForegroundColor Red
        Exit
    }
    return $appxPkg
}

Function Remove-ProviderAppxProvisionedPackage {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $AppxDisplayName )
    if ($Script:isNano) { return } # Skip if Nano
    $appxProvPkg = Get-AppxProvisionedPackage -Online | Where-Object { $_.DisplayName -eq $AppxDisplayName }
    if ($appxProvPkg -ne $null) {
        Remove-AppxProvisionedPackage -Online -PackageName $appxProvPkg.PackageName 2>&1 | Out-Null
        Write-Host "Removed AppxProvisionedPackage $($appxProvPkg.PackageName)"
    }
}

Function Remove-CmdletsPathFromPSModulePath {
    # This function removes cmdlet module path(s) from system PSModulePath environment variable.

    if ($Script:isServerCore) {
        $appxCmdletsPath = "$env:ProgramFiles\QLogic Corporation\PowerKit\Cmdlets"
    } else {
        $appxCmdletsPath = (Get-AppxPackageWrapper -AppxName 'QLGCProvider').InstallLocation + '\Cmdlets'
    }

    if (Test-RegistryValue -Path 'HKLM:\System\CurrentControlSet\Control\Session Manager\Environment' -Value 'PSModulePath') {
        $currPSModulePathArr = ((Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Session Manager\Environment').PSModulePath).Split(';')
        $newPSModulePathArr = @()
        # Remove any paths containing 'QLGCProvider' from PSModulePath
        foreach ($path in $currPSModulePathArr) {
            if (-not $path.Contains('QLGCProvider')) {
                $newPSModulePathArr += $path
            } else {
                Write-Host "Removed '$path' from PSModulePath."
            }
        }
        $newPSModulePathStr = $newPSModulePathArr -join ';'

        # Write PSModulePath to registry and set local session variable
        & setx /s $env:COMPUTERNAME PSModulePath $newPSModulePathStr /m 2>&1 | Out-Null
        $env:PSModulePath = $newPSModulePathStr
    }
}

Function Remove-CLSIDRegistryPaths {
    if (Test-Path -Path "Registry::HKCR\CLSID\$Script:CLSID") {
        Remove-Item -Path "Registry::HKCR\CLSID\$Script:CLSID" -Recurse
        Write-Host "Removed HKCR:\CLSID\$Script:CLSID from registry."
    }
    if (Test-Path -Path "HKLM:\Software\Classes\CLSID\$Script:CLSID") {
        Remove-Item -Path "HKLM:\Software\Classes\CLSID\$Script:CLSID" -Recurse
        Write-Host "Removed HKLM:\Software\Classes\CLSID\$Script:CLSID from registry."
    }
}

Function Uninstall-MofComp {
    # TODO - Determine if this function is always necessary
    if ($Script:isServerCore) {
        $mofCompFilePath = "$env:ProgramFiles\QLogic Corporation\PowerKit\QLGCProviderUninstall.mof"
    } else {
        $mofCompFilePath = (Get-AppxPackageWrapper -AppxName 'QLGCProvider').InstallLocation
        $mofCompFilePath += '\QLGCProviderUninstall.mof'
    }

    # $mofcompOutput = & mofcomp.exe -N:root\qlgcfastlinq -class:forceupdate $appxPath\QLGCProviderUninstall.mof 2>&1
    $mofcompOutput = & mofcomp.exe -N:root\qlgcfastlinq $mofCompFilePath 2>&1
    if ($mofcompOutput -match 'Error') {
        Write-Host "Failed to unregister `"$mofCompFilePath`": $($mofcompOutput -match 'Error')" -ForegroundColor Red
    } else {
        Write-Host "QLGCProvider unregistered."
    }
    if (Test-RegistryValue -Path 'HKLM:\Software\Microsoft\Wbem\CIMOM\SecuredHostProviders' -Value 'Root/qlgcfastlinq:__Win32Provider.Name="QLGCProvider"') {
        Remove-ItemProperty -Path 'HKLM:\Software\Microsoft\Wbem\CIMOM\SecuredHostProviders' -Name 'Root/qlgcfastlinq:__Win32Provider.Name="QLGCProvider"'
    }
}

Function Remove-CmdletsFromProgramData {
    # Remove any ProgramData directories containing 'QLGCProvider_'
    $progDataPaths = (Get-ChildItem $env:ProgramData | Where-Object { ($_.Name).StartsWith('QLGCProvider_') }).FullName
    foreach ($path in $progDataPaths) {
        Remove-Item $path -Recurse -Force
        Write-Host "Cmdlets removed from `"$path`"."
    }
}

Function Remove-CmdletsFromProgramFiles {
    Remove-Item -Path "$env:ProgramFiles\QLogic Corporation\PowerKit" -Recurse -Force 2>&1 | Out-Null
}

Function Remove-RESTFilesFromProgramData {
    # Remove any ProgramData directories containing 'QLGCProviderREST_'
    $progDataPaths = (Get-ChildItem $env:ProgramData | Where-Object { ($_.Name).StartsWith('QLGCProviderREST_') }).FullName
    foreach ($path in $progDataPaths) {
        Remove-Item $path -Recurse -Force
        Write-Host "REST files removed from `"$path`"."
    }
}

Function Remove-AppxPackageWrapper {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $AppxDisplayName )
    $appxPkg = Get-AppxPackage | Where-Object { $_.Name -eq $AppxDisplayName }
    if ($appxPkg -ne $null) {
        $savedProgressPreference = $Global:ProgressPreference
        $Global:ProgressPreference = 'SilentlyContinue'
        Remove-AppxPackage -Package $appxPkg 2>&1 | Out-Null
        $Global:ProgressPreference = $savedProgressPreference
        Write-Host "Removed $AppxDisplayName AppxPackage."
    }
}

Function Remove-FromIIS {
    $appcmd = "$env:SystemRoot\system32\inetsrv\appcmd.exe"

    # Stop all sites
    $sites = (& $appcmd list site 2>&1)
    if ($sites -ne $null) {
        if ($sites.GetType() -eq [System.String]) {
            $siteName = $sites.Split('"')[1]
            $appcmdOutput = (& $appcmd stop site $siteName 2>&1)
        } else {
            foreach ($s in $sites) {
                $siteName = $s.Split('"')[1]
                $appcmdOutput = (& $appcmd stop site $siteName 2>&1)
            }
        }
    }

    # Stop DefaultAppPool and MOData apppool
    $appcmdOutput = (& $appcmd stop apppool /apppool.name:DefaultAppPool 2>&1)
    $appcmdOutput = (& $appcmd stop apppool /apppool.name:MOData 2>&1)

    # Delete MOData appool
    $appcmdOutput = (& $appcmd delete apppool /apppool.name:MOData 2>&1)

    # Delete MODataSvc app
    $appcmdOutput = (& $appcmd delete app MODataSvc/MODataSvc 2>&1)

    # Delete MODataSvc site
    $appcmdOutput = (& $appcmd delete site MODataSvc 2>&1)
    Remove-Item "$env:HOMEDRIVE\inetpub\wwwroot\MOData" -Recurse -Force

    # Start DefaultAppPool apppool
    $appcmdOutput = (& $appcmd start apppool /apppool.name:DefaultAppPool 2>&1)

    # Start all sites
    $sites = (& $appcmd list site 2>&1)
    if ($sites -ne $null) {
        if ($sites.GetType() -eq [System.String]) {
            $siteName = $sites.Split('"')[1]
            $appcmdOutput = (& $appcmd start site $siteName 2>&1)
        } else {
            foreach ($s in $sites) {
                $siteName = $s.Split('"')[1]
                $appcmdOutput = (& $appcmd start site $siteName 2>&1)
            }
        }
    }

    # Remove any existing firewall rules
    if ((Get-NetFirewallRule | Where-Object { ($_.DisplayName).StartsWith('MOData_IIS_Port') }) -ne $null) {
        Remove-NetFirewallRule -DisplayName 'MOData_IIS_Port'
    }

    Write-Host "Removed from IIS."
}

Function Test-LastExitCode {
    Param (
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $ExitCode,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Message,
        [Parameter(Mandatory=$false)] [AllowNull()] [Switch] $ExitScriptIfBadErrorCode
    )
    if ($ExitCode -eq 0) { return; }

    if (-not $ExitScriptIfBadErrorCode) {
        Write-Host "WARNING: $Message" -ForegroundColor Yellow
    } else {
        Write-Host "ERROR: $Message" -ForegroundColor Red
        Exit
    }
}


#--------------------------------------------------------------------------------------------------
# Globals
$Script:CLSID = '{BC33E6C0-DB6A-4781-B924-CB1CDA88E4A1}'
$Script:isNano = Test-IfNano
$Script:isServerCore = Test-IfServerCore

#--------------------------------------------------------------------------------------------------
# Script - Cmdlets Uninstall
if (-not $UninstallRESTServerOnly) {
    if ($Script:isServerCore) {
        Remove-CmdletsPathFromPSModulePath
        Remove-CLSIDRegistryPaths
        Uninstall-MofComp
        Remove-CmdletsFromProgramFiles
    } else {
        Remove-ProviderAppxProvisionedPackage -AppxDisplayName 'QLGCProvider'
        Remove-CmdletsPathFromPSModulePath
        Remove-CLSIDRegistryPaths
        Uninstall-MofComp
        Remove-CmdletsFromProgramData
        Remove-AppxPackageWrapper -AppxDisplayName 'QLGCProvider'
    }
    Write-Host "Successfully uninstalled FastLinq PowerKit.`n" -ForegroundColor Green
}

#--------------------------------------------------------------------------------------------------
# Script - Old REST API Uninstall
$oldRestIsInstalled = (Get-AppxPackage | Where-Object { $_.Name -eq 'QLGCProviderREST' }) -ne $null
if ($oldRestIsInstalled) {
    Remove-ProviderAppxProvisionedPackage -AppxDisplayName 'QLGCProviderREST'
    Remove-RESTFilesFromProgramData
    Remove-AppxPackageWrapper -AppxDisplayName 'QLGCProviderREST'
    Remove-FromIIS
    Write-Host 'Successfully uninstalled FastLinq REST API PowerKit.' -ForegroundColor Green
}

#--------------------------------------------------------------------------------------------------
# Script - New REST API Uninstall
$restServerRegistryPath = 'HKLM:\Software\QLogic Corporation\PowerShell Cmdlets REST Server'
if (Test-Path -Path $restServerRegistryPath) {

    # Check if running inside a container
    $runningInContainer = $env:USERNAME -eq 'ContainerAdministrator' -and $env:USERDOMAIN -eq 'User Manager'

    # Get values from registry
    $restServerInstallPath = (Get-ItemProperty -Path $restServerRegistryPath).path
    $restServerProtocol = (Get-ItemProperty -Path $restServerRegistryPath).protocol
    $restServerHttpPort = (Get-ItemProperty -Path $restServerRegistryPath).http_port
    $restServerHttpsPort = (Get-ItemProperty -Path $restServerRegistryPath).https_port
    $restServerThumbprint = (Get-ItemProperty -Path $restServerRegistryPath).thumbprint

    # Uninstall Windows service
    Write-Host "`nUninstalling Windows service" -ForegroundColor Cyan
    $null = & "$restServerInstallPath\PowerShellCmdletsRestServer.exe" -uninstall
    Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to uninstall Windows service" -ExitScriptIfBadErrorCode


    # Wait for service to stop before continuing
    Write-Host "`nWaiting for service to stop before continuing." -ForegroundColor Cyan
    $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
    $timeoutSecs = 60
    while ($true) {
        Start-Sleep -Milliseconds 2500
        $service = Get-Service -Name $restServerServiceName -ErrorAction SilentlyContinue
        if ($service -eq $null) {
            Write-Host "... service not found with name '$restServerServiceName'; assuming it was successfully deleted." -ForegroundColor Cyan
            break;
        }
        if ($service -ne $null -and $service.Status -eq 'Stopped') {
            Write-Host '... service stopped!' -ForegroundColor Cyan
            break;
        }
        if ($stopwatch.Elapsed.TotalSeconds -gt $timeoutSecs) {
            Test-LastExitCode -ExitCode 1 -Message "`n$restServerServiceName service never stopped after $timeoutSecs seconds; giving up." -ExitScriptIfBadErrorCode
        }
        Write-Host 'Still waiting for service to stop ...' -ForegroundColor Cyan
    }


    # Delete installation directory
    Write-Host "`nDeleting installation directory" -ForegroundColor Cyan
    Remove-Item -Path $restServerInstallPath -Recurse -Force

    if ($restServerProtocol -eq 'http' -or $restServerProtocol -eq 'both') {
        # Unregister URL with Windows
        Write-Host "`nUnregistering HTTP URL with Windows" -ForegroundColor Cyan
        $null = netsh http delete urlacl url="http://localhost:$restServerHttpPort/"
        Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to unregister HTTP URL with Windows."
    }

    if ($restServerProtocol -eq 'https' -or $restServerProtocol -eq 'both') {
        
        # Unregister URL with Windows
        Write-Host "`nUnregistering HTTPS URL with Windows" -ForegroundColor Cyan
        $null = netsh http delete urlacl url="https://localhost:$restServerHttpsPort/"
        Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to unregister HTTPS URL with Windows."
        $null = netsh http delete sslcert ipport="0.0.0.0:$restServerHttpsPort"
        
        # Delete certificate
        if ($restServerThumbprint -ne $null) {
            Write-Host "`nDeleting HTTPS certificate" -ForegroundColor Cyan
            dir Cert:\LocalMachine -Recurse | ? Thumbprint -Match $restServerThumbprint | Remove-Item
        } else {
            Write-Host "`nNOT deleting HTTPS certificate" -ForegroundColor Cyan
        }
    }

    # Delete Windows registry entries
    if (Test-Path -Path $restServerRegistryPath) {
        Write-Host "`nDeleting Windows registry entries" -ForegroundColor Cyan
        Remove-Item -Path $restServerRegistryPath -Recurse
    }

    # Remove any existing firewall rules
    if (-not $runningInContainer) {
        $fwRules = Get-NetFirewallRule | Where-Object { ($_.DisplayName).StartsWith('QLogic PowerShell Cmdlets REST Server') }
        foreach ($fwRule in $fwRules) {
            Remove-NetFirewallRule -DisplayName $fwRule.DisplayName
        }
    }
    Write-Host "`nSuccessfully uninstalled FastLinq PowerKit REST Server." -ForegroundColor Green
}
else {
    Write-Host 'Could not uninstall REST server because it does not seem to be installed.' -ForegroundColor Red
}