InstallMarvellQLogicFCPowerKit.psm1

# Script level parameters
Param (
    [Switch] $ConfigureWinRM = $false,  # For silent WinRM configuration
    [Switch] $AddIISAccount = $false,   # Avoids a prompt - still need to enter username and password though.
    [Switch] $SkipEnablingWindowsFeaturesDEBUG = $false
)


# Helper Functions

Function Out-Log {
    # This function controls console output, displaying it only if the Verbose variable is set.
    # This function now has another parameter, Level. Commands that may output messages now have a level parameter set. Key is below.
    # Level = 0 - Completely silent except for success / failure messages.
    # Level = 1 - Default if $Script:verbose is not set, about 5 output messages per added appx.
    # Level = 2 - Level 1 + Appcmd output messages, Add-AppxPackage messages
    # Level = 3 - Level 2 + Registry, XML, enabling of features, WinRM, setx, IO, netsh, etc. Very verbose.
    Param (
        [Parameter(Mandatory=$true)] [AllowNull()] $Data,
        [Parameter(Mandatory=$true)] [AllowNull()] $Level,
        [Parameter(Mandatory=$false)] [AllowNull()] $ForegroundColor,
        [Parameter(Mandatory=$false)] [AllowNull()] [Switch] $NoNewline
    )
    if ($Script:Verbose -eq $null) { $Script:Verbose = 1 }
    if ($Data -eq $null) { return }
    $Data = ($Data | Out-String)
    $Data = $Data.Substring(0, $Data.Length - 2)  # Remove extra `n from Out-String (only one)
    $Data = $Data.Replace('"', '`"')
    $expression = "Write-Host -Object `"$Data`""
    if ($ForegroundColor -ne $null) { $expression += " -ForegroundColor $ForegroundColor" }
    if ($NoNewline) { $expression += " -NoNewline" }
    if ($Level -le $Script:verbose) {
        Invoke-Expression $expression
    }
}

Function Test-ExecutionPolicy {
    # This function checks to make sure the ExecutionPolicy is not Unrestricted and if it is, prompts the user to change it to either RemoteSigned or Bypass.
    # If the ExecutionPolicy is left at Unrestricted, the user will receive one prompt for every cmdlet each time they open a new PowerShell window.
    $currentExecutionPolicy = Get-ExecutionPolicy
    if ($currentExecutionPolicy -eq 'Unrestricted') {

        $title = 'Execution Policy Configuration'
        $message = "Your current execution policy is $currentExecutionPolicy. This policy causes PowerShell to prompt you every session for every cmdlet. Set the execution policy to RemoteSigned or Bypass to avoid this."
        $remoteSigned = New-Object System.Management.Automation.Host.ChoiceDescription '&RemoteSigned', `
            'Sets the execution policy to RemoteSigned.'
        $bypass = New-Object System.Management.Automation.Host.ChoiceDescription '&Bypass', `
            'Sets the execution policy to Bypass.'
        $ignoreAndContinue = New-Object System.Management.Automation.Host.ChoiceDescription '&Ignore and Continue', `
            'Does not change the execution policy.'
        $options = [System.Management.Automation.Host.ChoiceDescription[]]($remoteSigned, $bypass, $ignoreAndContinue)
        $tempResult = $host.ui.PromptForChoice($title, $message, $options, 0)


        if ($tempResult -eq 0) { $newExecutionPolicy = 'RemoteSigned' }
        if ($tempResult -eq 1) { $newExecutionPolicy = 'Bypass' }
        if ($tempResult -eq 2) { return }

        # If the user has set a CurrentUser sceope level ExecutionPolicy, then change this to RemoteSigned/Bypass as well.
        if ((Get-ExecutionPolicy -Scope CurrentUser) -ne 'Undefined') {
            Set-ExecutionPolicy -ExecutionPolicy $newExecutionPolicy -Scope CurrentUser -Force
        }
        Set-ExecutionPolicy -ExecutionPolicy $newExecutionPolicy -Scope LocalMachine -Force

        Out-Log -Data "ExecutionPolicy changed from $currentExecutionPolicy to $(Get-ExecutionPolicy)." -Level 0
    }
}


Function Get-CmdletsAppxFileName {
    return '.\MRVLFCProvider-WindowsServer.appx'
}

Function Add-ManyToRegistryWriteOver {
    # This function changes multiple registry values at once.
    # Path is the path to the registry key to change, e.g. "HKLM:\Software\Classes\CLSID\{A31B8A5E-8B6A-421C-8BB1-F85C9C34E659}\InprocServer32"
    # NameTypeValueArray is an array of arrays of size 3 containing Name, Type, and Value for the registry entry, e.g.
        # @(
            # @('(Default)', 'String', "C:\Program Files\WindowsApps\MRVLFCProvider_1.0.1.0_x64_NorthAmerica_yym61f5wjvd3m\MRVLFCProvider.dll"),
            # @('ThreadingModel', 'String', 'Free')
        # )

    Param (
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Path,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $NameTypeValueArray
    )
    # if path doesn't exist, create it
    if (-not (Test-Path -LiteralPath $Path)) {
        Out-Log -Data (New-Item -Path $Path -Force 2>&1) -Level 3
    }

    $arr = $NameTypeValueArray
    if ($arr[0] -isnot [System.Array]) {
        Out-Log -Data (New-ItemProperty -LiteralPath $Path -Name $arr[0] -Type $arr[1] -Value $arr[2] -Force 2>&1) -Level 3
        return
    }
    foreach ($ntv in $arr) {
        Out-Log -Data (New-ItemProperty -LiteralPath $Path -Name $ntv[0] -Type $ntv[1] -Value $ntv[2] -Force 2>&1) -Level 3
    }
}

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

    try {
        Out-Log -Data (Get-ItemProperty -Path $Path | Select-Object -ExpandProperty $Value -ErrorAction Stop 2>&1) -Level 3
        return $true
    } catch {
        return $false
    }
}


Function Convert-CredentialToHeaderAuthorizationString {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Credential )
    return [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$($Credential.UserName)`:$(ConvertFrom-SecureToPlain -SecurePassword $Credential.Password)"))
}

Function Get-AppxPackageWrapper {
    # This function tries the Get-AppxPackage command several times before really failing.
    # This is necessary to prevent some intermittent issues with Get-AppxPackage.
    # This may no longer be necessary as it seems to only happen after an Add-AppxProvisionedPackage which is no longer used.
    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) { return $appxPkg }
        Out-Log -Data ("Couldn't find Appx package. Trying again in $numSecondsBetweenAttempts seconds (attempt $($i+1) of $numAttempts) ...") -ForegroundColor DarkRed -Level 1
        Start-Sleep -Seconds $numSecondsBetweenAttempts
    }
    Out-Log -Data 'Failed to find Appx package. Please try running this script again.' -ForegroundColor Red -Level 0
    Exit
}

Function Invoke-IOWrapper {
    # This function tries the Expression several times before really failing.
    # This is necessary to prevent some intermittent issues with certain IO commands, especially Copy-Item.
    Param (
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Expression,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $SuccessMessage,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $FailMessage
    )

    $numAttempts = 5
    $numSecondsBetweenAttempts = 1
    $Expression += ' -ErrorAction Stop'
    for ($i=0; $i -lt $numAttempts; $i++) {
        try {
            Invoke-Expression $Expression
            if ($i -ne 0) { Out-Log -Data 'Success!' -ForegroundColor DarkGreen -Level 3 }
            Out-Log -Data $SuccessMessage -Level 1
            return
        } catch {
            if ($i -ne 0) { Out-Log -Data 'Failed.' -ForegroundColor DarkRed -Level 3 }
            Out-Log -Data "$FailMessage Trying again in $numSecondsBetweenAttempts seconds (attempt $($i+1) of $numAttempts) ... " -ForegroundColor DarkRed -NoNewline -Level 3
            Start-Sleep -Seconds $numSecondsBetweenAttempts
            $Global:err = $_
        }
    }
    Out-Log -Data 'Failed.' -ForegroundColor DarkRed -Level 3
    Out-Log -Data "$FailMessage Failed after $numAttempts attempts with $numSecondsBetweenAttempts second(s) between each attempt." -ForegroundColor Red -Level 0
    Out-Log -Data "Failed to install $Script:CurrentInstallation." -ForegroundColor Red -Level 0
    Exit
}


# Prompts
Function Set-WinRMConfigurationPrompt {
    if ($ConfigureWinRM) { return $true }

    $title = 'WinRM Configuration'
    $message = 'Do you want to configure WinRM in order to connect to Linux machines remotely?'
    $yes = New-Object System.Management.Automation.Host.ChoiceDescription '&Yes', `
        'Configures WinRM to allow remote connections to Linux machines.'
    $no = New-Object System.Management.Automation.Host.ChoiceDescription '&No', `
        'Does not configure WinRM. Remote connection to Linux machines will not be possible.'
    $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
    $tempResult = $host.ui.PromptForChoice($title, $message, $options, 0)
    if ($tempResult -eq 0) { return $true } else { return $false }
}


Function Get-CredentialWrapper {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $AddAccountToIISResponse )
    if (-not $AddAccountToIISResponse) { return $null }

    try {
        return Get-Credential -Message 'Enter the credentials for the account you would like to be added to IIS' -UserName $env:USERNAME
    } catch {
        Out-Log -Data 'No credentials supplied; no account will be added to IIS.' -Level 1
    }
}


# Main Functions

Function Add-AppxTrustedAppsRegistryKey {
    # This function remembers user's current AllowAllTrustedApps registry setting and then changes it to
    # allow installation of all trusted apps.

    # Remember user's registry setting before modify it to be able to set it back later.
    $appxPolicyRegKey = 'HKLM:\Software\Policies\Microsoft\Windows\Appx'
    $appxPolicyRegValName = 'AllowAllTrustedApps'
    if (Test-Path -Path $appxPolicyRegKey) {
        $appxPolicyRegValData = (Get-ItemProperty -Path $appxPolicyRegKey).$appxPolicyRegValName
        if ($appxPolicyRegValData -ne $null) {
            $Script:savedAppxPolicyRegValData = $appxPolicyRegValData
        }
        Start-Sleep -Milliseconds 1000  # Otherwise access denied error when do Add-ManyToRegistryWriteOver below
    }

    Add-ManyToRegistryWriteOver -Path $appxPolicyRegKey -NameTypeValueArray @(
        @($appxPolicyRegValName, 'DWord', 1) )
}

Function Remove-CLSIDRegistryPaths {
    # if (-not $Script:isNano) { return } # Skip if NOT Nano
    if (Test-Path -Path "Registry::HKCR\CLSID\$Script:CLSID") {
        Remove-Item -Path "Registry::HKCR\CLSID\$Script:CLSID" -Recurse
    }
    if (Test-Path -Path "HKLM:\Software\Classes\CLSID\$Script:CLSID") {
        Remove-Item -Path "HKLM:\Software\Classes\CLSID\$Script:CLSID" -Recurse
    }
}

Function Add-AppxPackageWrapper {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $AppxFileName )

    $savedProgressPreference = $Global:ProgressPreference
    $Global:ProgressPreference = 'SilentlyContinue'
    Out-Log -Data (Add-AppxPackage $AppxFileName 2>&1) -Level 2
    $Global:ProgressPreference = $savedProgressPreference
    Out-Log -Data "Added '$($AppxFileName.Replace('.\', ''))' AppxPackage." -Level 1
}

Function Add-CLSIDRegistryPathsAndMofCompInstall {
    # if ($Script:isNano) { return } # Skip if Nano
    # Perform steps of Register-CimProvider.exe -Namespace Root/qlgcfc -ProviderName MRVLFCProvider -Path MRVLFCProvider.dll -verbose -ForceUpdate -HostingModel LocalSystemHost
    $appxPath = (Get-AppxPackageWrapper -AppxName 'MRVLFCProvider').InstallLocation
    $mofcompOutput = & mofcomp.exe -N:root\qlgcfc $appxPath\MRVLFCProvider.mof 2>&1
    if ($mofcompOutput -match 'Error') {
        Out-Log -Data "Failed to register '$appxPath\MRVLFCProvider.mof': $($mofcompOutput -match 'Error')" -ForegroundColor Red -Level 1
    }

    Add-ManyToRegistryWriteOver -Path "HKLM:\Software\Classes\CLSID\$Script:CLSID" -NameTypeValueArray @(
        @('(Default)', 'String', 'MRVLFCProvider') )
    Add-ManyToRegistryWriteOver -Path "HKLM:\Software\Classes\CLSID\$Script:CLSID\InprocServer32" -NameTypeValueArray @(
        @('(Default)', 'String', "$appxPath\MRVLFCProvider.dll"),
        @('ThreadingModel', 'String', 'Free') )
}

Function Remove-AppxTrustedAppsRegistryKey {

    $appxPolicyRegKey = 'HKLM:\Software\Policies\Microsoft\Windows\Appx'
    $appxPolicyRegValName = 'AllowAllTrustedApps'

    # If user previously had this registry key, set it back to what it was before. Otherwise, just delete the registry key.
    if ($Script:savedAppxPolicyRegValData -ne $null) {
        Add-ManyToRegistryWriteOver -Path $appxPolicyRegKey -NameTypeValueArray @(
            @($appxPolicyRegValName, 'DWord', $Script:savedAppxPolicyRegValData) )
    } else {
        Remove-ItemProperty -Path $appxPolicyRegKey -Name $appxPolicyRegValName
    }
}

Function Copy-CmdletsToProgramData {
    # Check for ProgramData directories with old versions and remove them
    $oldPaths = (Get-ChildItem $env:ProgramData | Where-Object { ($_.Name).StartsWith('MRVLFCProvider_') }).FullName
    foreach ($oldPath in $oldPaths) {
        Remove-Item $oldPath -Recurse -Force
    }

    # Copy cmdlets to ProgramData
    $appxPkg = Get-AppxPackageWrapper -AppxName 'MRVLFCProvider'
    $cmdletsPath = $appxPkg.InstallLocation + '\Cmdlets\'
    if (Test-Path $cmdletsPath) {
        $cmdletsDestPath = "$env:ProgramData\$($appxPkg.PackageFullName)"
        Invoke-IOWrapper -Expression "Copy-Item -Path `"$cmdletsPath`" -Destination $cmdletsDestPath -Recurse -Force" `
            -SuccessMessage "Cmdlets copied to '$cmdletsDestPath'." `
            -FailMessage "Failed to copy FC cmdlets."
    }
}


Function Add-CmdletsPathToPSModulePath {
    #$p = [Environment]::GetEnvironmentVariable("PSModulePath")
    #Out-Log -Data "PSModulePath = '$p'" -Level 1
    # This function adds cmdlet module path to system PSModulePath environment variable permanently.
    # Out-Log -Data "PSModulePath = '$appxCmdletsPath' to PSModulePath." -Level 1
    $appxCmdletsPath = (Get-AppxPackageWrapper -AppxName 'MRVLFCProvider').InstallLocation + '\Cmdlets'
    if (Test-RegistryValue -Path 'HKLM:\System\CurrentControlSet\Control\Session Manager\Environment' -Value 'PSModulePath') {
        $currPSModulePathArr = [Environment]::GetEnvironmentVariable("PSModulePath").Split(';')
        #Out-Log -Data "currPSModulePathArr '$currPSModulePathArr'" -Level 1
        $newPSModulePathArr = @()
        # Remove any old paths with MRVLFCProvider from PSModulePath
        foreach ($path in $currPSModulePathArr) {
            if (-not $path.Contains('MRVLFCProvider')) {
                # Out-Log -Data "path '$path'" -Level 1
                $newPSModulePathArr += $path
            }
        }
        # Add new cmdlet path to PSModulePath
        $newPSModulePathArr += $appxCmdletsPath  # Skip for uninstall script
        $newPSModulePathStr = $newPSModulePathArr -join ';'

        #Out-Log -Data "new PSModulePath '$newPSModulePathArr'" -Level 1
        # Write PSModulePath to registry and set local session variable
        Out-Log -Data (& setx /s $env:COMPUTERNAME PSModulePath $newPSModulePathStr /m 2>&1) -Level 3
        $env:PSModulePath = $newPSModulePathStr
        Out-Log -Data "Added '$appxCmdletsPath' to PSModulePath." -Level 1

    } else {
        # No PSModulePath registry key/value exists, so don't worry about modifying it. Just create a new one.
        Out-Log -Data (& setx /s $env:COMPUTERNAME PSModulePath $appxCmdletsPath /m 2>&1) -Level 3
        $env:PSModulePath = $appxCmdletsPath
        Out-Log -Data "Added '$appxCmdletsPath' to PSModulePath." -Level 1
    }
}

Function Set-WinRMConfiguration {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $SetWinRMConfigurationResponse )
    if ($SetWinRMConfigurationResponse) {
        Out-Log -Data (& winrm set winrm/config/client '@{AllowUnencrypted="true"}' 2>&1) -Level 3
        Out-Log -Data (& winrm set winrm/config/client '@{TrustedHosts="*"}' 2>&1) -Level 3
        Out-Log -Data 'WinRM successfully configured.' -Level 1
    }
}


Function Test-PowerShellVersion {
    # This function tests the version of PowerShell / Windows Management Framework and prompts the user
      # to download a newer version if necessary.
    if ($PSVersionTable.PSVersion.Major -ge 4) { return }

    Out-Log -Data 'Failed to install FastLinq REST API PowerKit: Windows Management Framework 4.0+ required.' -ForegroundColor Red -Level 0
    Out-Log -Data ' Please install WMF 4.0 or higher and run this script again.' -ForegroundColor Red -Level 0
    Out-Log -Data ' URL for WMF 4.0: https://www.microsoft.com/en-us/download/details.aspx?id=40855' -ForegroundColor Red -Level 0
    Out-Log -Data ' URL for WMF 5.0: https://www.microsoft.com/en-us/download/details.aspx?id=50395' -ForegroundColor Red -NoNewline -Level 0

    $title = 'Download Windows Management Framework 4.0+'
    $message = 'Windows Management Framework 4.0+ is required for FastLinq REST API PowerKit. Which WMF version would you like to download?'
    $4 = New-Object System.Management.Automation.Host.ChoiceDescription '&4.0', `
        'Windows Management Framework 4.0 (https://www.microsoft.com/en-us/download/details.aspx?id=40855)'
    $5 = New-Object System.Management.Automation.Host.ChoiceDescription '&5.0', `
        'Windows Management Framework 5.0 (https://www.microsoft.com/en-us/download/details.aspx?id=50395'
    $none = New-Object System.Management.Automation.Host.ChoiceDescription '&None', `
        'None'
    $options = [System.Management.Automation.Host.ChoiceDescription[]]($4, $5, $none)
    $tempResult = $host.ui.PromptForChoice($title, $message, $options, 1)
    if ($tempResult -eq 0) {
        Start-Process 'https://www.microsoft.com/en-us/download/details.aspx?id=40855'
    } elseif ($tempResult -eq 1) {
        Start-Process 'https://www.microsoft.com/en-us/download/details.aspx?id=50395'
    }
    # TODO - Add script exiting message.
    Exit
}

Function Enable-WindowsFeatures {
    # This function enables the Windows features required for REST API to work.
    # This function does not check if the Windows feature is already enabled or not,
    # but enabling an already-enabled feature is not harmful.
    if ( $SkipEnablingWindowsFeaturesDEBUG) { return }
    Out-Log -Data 'Installing Management OData Service...' -Level 1

    $allFeatureNames = 'ManagementOdata, WCF-HTTP-Activation45, IIS-BasicAuthentication, IIS-WindowsAuthentication, IIS-WebServerManagementTools, IIS-ManagementConsole, IIS-ManagementScriptingTools, IIS-ManagementService, IIS-IIS6ManagementCompatibility, IIS-Metabase, IIS-WMICompatibility, IIS-LegacyScripts, IIS-LegacySnapIn' -split ', '
    for ($i=0; $i -lt $allFeatureNames.Count; $i++) {
        $featureName = $allFeatureNames[$i]
        Write-Progress -Activity 'Enabling required Windows features' -Status "Enabling $featureName ($($i+1) of $($allFeatureNames.Count))" -PercentComplete ($i / $allFeatureNames.Count * 100)
        Out-Log -Data (Enable-WindowsOptionalFeature -Online -FeatureName $featureName -All -NoRestart 2>&1) -Level 3
    }
    Write-Progress -Activity "Completed" -Completed
}

Function Copy-DependentLibsAndFilesToWbemPath 
{
    $appxPkg = Get-AppxPackageWrapper -AppxName 'MRVLFCProvider'
    $dependentLibPath = $appxPkg.InstallLocation

    if (Test-Path $dependentLibPath)
    {
        $dependentLibDestPath = "C:\Windows\System32\wbem"
        
        # Copy ql2xhai2.dll to "C:\Windows\System32\wbem"
        Invoke-IOWrapper -Expression "Copy-Item -Path `"$dependentLibPath\ql2xhai2.dll`" -Destination `"$dependentLibDestPath`" -Recurse -Force" `
            -SuccessMessage "Copied dependent library to Wbem path : '$dependentLibDestPath'." `
            -FailMessage "Failed to copy dependent library : '$dependentLibDestPath'. Please uninstall the old version if present. Please stop 'WMI Provider Host' if running."
            

        # Copy adapters_adapter_provider.properties to "C:\Windows\System32\wbem"
        Invoke-IOWrapper -Expression "Copy-Item -Path `"$dependentLibPath\adapters_adapter_provider.properties`" -Destination `"$dependentLibDestPath`" -Recurse -Force" `
            -SuccessMessage "Copied dependent file - adapters_adapter_provider.properties to Wbem path : '$dependentLibDestPath'." `
            -FailMessage "Failed to copy dependent file - adapters_adapter_provider.properties : '$dependentLibDestPath'. Please uninstall the old version if present. Please stop 'WMI Provider Host' if running."
            
            
        # Copy adapters_cna_provider.properties to "C:\Windows\System32\wbem"
        Invoke-IOWrapper -Expression "Copy-Item -Path `"$dependentLibPath\adapters_cna_provider.properties`" -Destination `"$dependentLibDestPath`" -Recurse -Force" `
            -SuccessMessage "Copied dependent file - adapters_cna_provider.properties to Wbem path : '$dependentLibDestPath'." `
            -FailMessage "Failed to copy dependent file - adapters_cna_provider.properties : '$dependentLibDestPath'. Please uninstall the old version if present. Please stop 'WMI Provider Host' if running."
            
            
        # Copy nvramdefs to "C:\Windows\System32\wbem"
        Invoke-IOWrapper -Expression "Copy-Item -Path `"$dependentLibPath\nvramdefs`" -Destination `"$dependentLibDestPath\nvramdefs`" -Recurse -Force" `
            -SuccessMessage "Copied dependent files - nvramdefs to Wbem path : '$dependentLibDestPath'." `
            -FailMessage "Failed to copy dependent files - nvramdefs : '$dependentLibDestPath'. Please uninstall the old version if present. Please stop 'WMI Provider Host' if running."              
    }
}

Function Install-WebServiceEndPoint {
    # This function configures IIS and sets up the web service endpoint and is an adaptation of the
    # 'SetupIISConfig.ps1' from the 'Windows Powershell role-based OData Web Service sample' project.
    # This is a massive function containing several helper functions within it.
    Param (
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Site,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Path,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Cfgfile,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Port,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $App,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $AppPool,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Svc,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Schema,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $DispatchXml,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Rbac,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $CustomPluginAssembly
    )

    Function Backup-IISConfig {
        $dt = Get-Date
        $backupID = "MODataIISSetup_$($dt.Year)-$($dt.Month)-$($dt.Day)-$($dt.Hour)-$($dt.Minute)-$($dt.Second)"
        Out-Log -Data (& $Script:appcmd add backup $backupID 2>&1) -Level 2
    }

    Function Copy-FilesToCorrectLocation {
        if (-not (Test-Path $Path)) {
            Out-Log -Data (New-Item -Path $Path -ItemType Container 2>&1) -Level 3
        }
        Copy-Item -Path $Cfgfile                -Destination (Join-Path $Path 'web.config')
        Copy-Item -Path $Svc                    -Destination $Path
        Copy-Item -Path $Schema                 -Destination $Path
        Copy-Item -Path $DispatchXml            -Destination $Path
        Copy-Item -Path $Rbac                   -Destination (Join-Path $Path 'RbacConfiguration.xml')
        Copy-Item -Path $CustomPluginAssembly   -Destination $Path
    }

    Function Invoke-ActionAllSites {
        Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Action )
        $siteInfoArray = (& $Script:appcmd list site)
        if ($siteInfoArray -eq $null) { return }
        foreach ($siteInfo in $siteInfoArray.Split("`n")) {
            $siteName = $siteInfo.split('"')[1]
            Invoke-ActionOneSite -SiteAction $Action -SiteName $siteName
        }
    }

    Function Invoke-ActionOneSite {
        Param (
            [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $SiteAction,
            [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $SiteName
        )
        Out-Log -Data (& $Script:appcmd $SiteAction site $SiteName 2>&1) -Level 2
    }

    Function New-SiteID {
        # Determine an unused ID number to use.
        $siteInfoArray = (& $Script:appcmd list site)
        if ($siteInfoArray -eq $null) { return 1 }
        $largestId = 0
        foreach ($siteInfo in $siteInfoArray.Split("`n")) {
            $i = $siteInfo.IndexOf('id:') + 3
            $c = $siteInfo.IndexOf(',', $i) - $i
            $id = $siteInfo.Substring($i, $c)
            if ($id -gt $largestId) { $largestId = $id }
        }
        return [int]$largestId + 1
    }

    Function Unlock-AuthSections {
        Out-Log -Data (& $Script:appcmd unlock config -section:access 2>&1) -Level 2
        Out-Log -Data (& $Script:appcmd unlock config -section:anonymousAuthentication 2>&1) -Level 2
        Out-Log -Data (& $Script:appcmd unlock config -section:basicAuthentication 2>&1) -Level 2
        Out-Log -Data (& $Script:appcmd unlock config -section:windowsAuthentication 2>&1) -Level 2
    }

    Function Set-RequiredAuthentication {
        Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $EffVdir )
        Out-Log -Data (& $Script:appcmd set config $EffVdir /section:system.webServer/security/authentication/anonymousAuthentication /enabled:false /commit:apphost 2>&1) -Level 2
        Out-Log -Data (& $Script:appcmd set config $EffVdir /section:system.webServer/security/authentication/basicAuthentication /enabled:true /commit:apphost 2>&1) -Level 2
        Out-Log -Data (& $Script:appcmd set config $EffVdir /section:system.webServer/security/authentication/windowsAuthentication /enabled:true /commit:apphost 2>&1) -Level 2
    }

    Function New-FirewallRule {
        Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $FirewallPort )
        # Remove any existing firewall rules
        if ((Get-NetFirewallRule | Where-Object { ($_.DisplayName).StartsWith('MOData_IIS_Port') }) -ne $null) {
            Out-Log -Data (Remove-NetFirewallRule -DisplayName 'MOData_IIS_Port') -Level 3
        }
        # Add new firewall rule
        Out-Log -Data (New-NetFirewallRule -DisplayName 'MOData_IIS_Port' -Direction Inbound -Action Allow -Protocol TCP -LocalPort $FirewallPort) -Level 3
    }

    # TODO Check if all the params above which are files exist before continuing
    # TODO Check if IIS is installed before continuing

    Out-Log -Data 'Setting up web service endpoint ...' -Level 1

    $appxPkg = Get-AppxPackageWrapper -AppxName 'MRVLFCProviderREST'
    $restFilesPath = "$env:ProgramData\$($appxPkg.PackageFullName)"
    Push-Location -Path $restFilesPath


    Out-Log -Data (& $Script:appcmd delete apppool MOData 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd delete site $Site 2>&1) -Level 2
    Backup-IISConfig
    Copy-FilesToCorrectLocation

    Invoke-ActionAllSites -Action 'Stop'

    Out-Log -Data (& $Script:appcmd stop apppool /apppool.name:DefaultAppPool 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd start apppool /apppool.name:DefaultAppPool 2>&1) -Level 2

    $siteID = New-SiteID

    Out-Log -Data (& $Script:appcmd add apppool /name:$AppPool 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd set apppool /apppool.name:$AppPool /managedRuntimeVersion:"v4.0" 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd add site /name:$Site /id:$siteID /bindings:http://*:$Port /physicalPath:$env:SystemDrive\inetpub\wwwroot 2>&1) -Level 2

    Out-Log -Data (& $Script:appcmd set site /site.name:$Site /[path=`'/`'].applicationPool:$AppPool 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd delete app $site/$app 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd add app /site.name:$site /path:/$app /physicalPath:$path 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd set app /app.name:$site/$app /applicationPool:$AppPool 2>&1) -Level 2

    Unlock-AuthSections
    $effVdir = "$site"
    Set-RequiredAuthentication -EffVdir $effVdir

    Invoke-ActionOneSite -SiteAction 'Start' -SiteName $Site

    New-FirewallRule -FirewallPort $port

    Invoke-ActionAllSites -Action 'Start'

    Out-Log -Data 'Web Service endpoint is setup. The source root URI is:' -Level 0
    Out-Log -Data " http://localhost:$Port/$Site/$($Svc.Substring(2, $Svc.Length - 2))/" -Level 0 -ForegroundColor Gray

    Pop-Location

    Start-Sleep -Seconds 5
}

Function Add-AccountToIIS {
    Param ( [Parameter(Mandatory=$true)] [AllowNull()] $Credential )
    if ($Credential -eq $null) {
        Out-Log -Data "No account added to IIS.`n" -Level 1
        return
    }
    Out-Log -Data (& $Script:appcmd set config MODataSvc/MODataSvc -section:system.web/identity /impersonate:false 2>&1) -Level 2

    Out-Log -Data (& $Script:appcmd set apppool MOData /processModel.identityType:SpecificUser 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd set apppool MOData /processModel.userName:$($Credential.UserName) 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd set apppool MOData /processModel.password:$(ConvertFrom-SecureToPlain -SecurePassword $Credential.Password) 2>&1) -Level 2

    Out-Log -Data (& $Script:appcmd start apppool /apppool.name:MOData 2>&1) -Level 2

    Out-Log -Data "Account '$($Credential.UserName)' added to IIS." -Level 1

    $encodedAuthString = Convert-CredentialToHeaderAuthorizationString -Credential $Credential
    Out-Log -Data 'Use this string in the REST API request header:' -Level 0
    Out-Log -Data " Authorization: Basic $encodedAuthString" -Level 0 -ForegroundColor Gray
}

Function Test-RESTAPI {
    Param ( [Parameter(Mandatory=$true)] [AllowNull()] $Credential )
    if ($Credential -eq $null) { return }

    $encodedAuthString = Convert-CredentialToHeaderAuthorizationString -Credential $Credential
    $headers = @{
        'Authorization' = "Basic $encodedAuthString";
        'Content-Type' = 'application/json';
        'Accept' = 'application/json,application/xml';
    }
    $url = "http://localhost:7000/MODataSvc/Microsoft.Management.OData.svc/QLGCFCAdapter?"

    Out-Log -Data "Testing REST API with $url" -ForegroundColor Cyan -Level 1

    try {
        $response = (Invoke-RestMethod -Method Get -Uri $url -Headers $headers -Credential $Credential).value
        $response = ($response | Out-String).Trim()
        Out-Log -Data "`n$response`n" -Level 1
    } catch {
        $Global:errRest = $_
        Out-Log -Data "REST API test failed" -ForegroundColor Red -Level 0
        Out-Log -Data " StatusCode: $($_.Exception.Response.StatusCode.value__)" -ForegroundColor Red -Level 0
        Out-Log -Data " StatusDescription: $($_.Exception.Response.StatusDescription)" -ForegroundColor Red -Level 0
    }
}

Function Import-AllCmdlets {
    # This function imports all MRVLFC cmdlets into the current PowerShell session.
    # This function is necessary to update any cmdlet and cmdlet help changes that have occurred from one PowerKit
    # version to another. One consequence of force importing the MRVLFC cmdlets is the user will be prompted if they
    # want to run software from an unpublished publisher when this function is called rather than the first time
    # intellisense is invoked after the PowerKit is installed. Note that this prompt only occurs if it is the user's
    # first time installing the PowerKit. Of course, in this scenario this function call is not necessary, since the
    # cmdlets will not need to be refreshed.
    # TODO - Determine if this is the first time PowerKit has been installed / if user already trusts QLGC publisher.
    $moduleNames = (Get-Module -ListAvailable | Where-Object { $_.Name.StartsWith('QLGCFC_') }).Name
    foreach ($moduleName in $moduleNames) {
        Import-Module $moduleName -Force
    }
}

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) {
        Out-Log -Data "WARNING: $Message" -ForegroundColor Yellow -Level 1
    } else {
        Out-Log -Data "ERROR: $Message" -ForegroundColor Red -Level 1
    }
}

Function Install-FCPowerKit {
    Param ( $response0, $response1, $response2 )
    # Encapulate installer logic here
    #--------------------------------------------------------------------------------------------------
    # Globals
    $Script:CLSID = '{A31B8A5E-8B6A-421C-8BB1-F85C9C34E659}'
    $Script:savedAppxPolicyRegValData = $null
    $cmdletsAppxFileName = Get-CmdletsAppxFileName
    # $restAppxFileName = '.\MRVLFCProviderREST-WindowsServer.appx'
    $Script:appcmd = "$env:SystemRoot\system32\inetsrv\appcmd.exe"
    $Script:netsh = "$env:windir\system32\netsh.exe"
    $Script:Verbose = 1


    #--------------------------------------------------------------------------------------------------
    # User Input
    Test-ExecutionPolicy
    if ((Get-AppxPackage -Name 'MRVLFCProvider') -ne $null) {
       Test-LastExitCode -ExitCode 1 -Message "Could not install PowerKit because it is already installed! Please uninstall the PowerKit first." -ExitScriptIfBadErrorCode
    }
    else {

        $response0 = Set-WinRMConfigurationPrompt

        #$response1 = Install-PowerShellODataPrompt
        #$response2 = Add-AccountToIISPrompt -InstallPSODataResponse $response1
        #$response0 = $WinRMConfiguration
        #$response1 = $Configure_IIS_OData_RESTAPI
        #$response2 = $AddAccountToIIS
        #$cred = $Credential


        #--------------------------------------------------------------------------------------------------
        # Script - Cmdlets
        $Script:CurrentInstallation = 'MRVL FC PowerKit'
        Add-AppxTrustedAppsRegistryKey
        Remove-CLSIDRegistryPaths
        Add-AppxPackageWrapper -AppxFileName $cmdletsAppxFileName
        Add-CLSIDRegistryPathsAndMofCompInstall
        Remove-AppxTrustedAppsRegistryKey
        Copy-DependentLibsAndFilesToWbemPath
        Copy-CmdletsToProgramData
        Add-CmdletsPathToPSModulePath
        Set-WinRMConfiguration -SetWinRMConfigurationResponse $response0
        Out-Log -Data "Successfully installed MarvellQLogicFCPowerKit.`n" -ForegroundColor Green -Level 0
     
        #--------------------------------------------------------------------------------------------------
        Import-AllCmdlets
        Out-Log -Data "Note: if this was an upgrade from a previous version, you may need to restart the system for the new provider to be loaded." -ForegroundColor Yellow -Level 0
    }
}

Export-ModuleMember -function Install-FCPowerKit