InstallFastLinqPowerKit.ps1
# Script level parameters Param ( [Switch] $ConfigureWinRM = $false, # For silent WinRM configuration [Switch] $InstallPoshSSHModule = $false, [Switch] $InstallRESTServer = $false, # For (mostly) silent REST server setup [Switch] $InstallRESTServerOnly = $false, [PSCredential] $Credential = $null, [String] [ValidateSet($null, 'http', 'https', 'HTTP', 'HTTPS', 'both')] $RESTServerProtocol = $null, [Int32] [ValidateRange(1, 65535)] $RESTServerPort = 0, [ValidateSet($null, $true, $false)] $CreateSelfSignedHTTPSCert = $null, [SecureString] $HTTPSCertPassword = $null ) # 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 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.OperatingSystemSKU -eq 143) -or ($envInfoCimInst.OperatingSystemSKU -eq 144) ) ) } 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 Get-CmdletsAppxFileName { if ($Script:isNano) { return '.\FastLinqProvider-WindowsServerNano.appx' } else { return '.\FastLinqProvider-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\{BC33E6C0-DB6A-4781-B924-CB1CDA88E4A1}\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\QLGCProvider_1.0.0.5_x64_NorthAmerica_yym61f5wjvd3m\qlgcProvider.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 ConvertFrom-SecureToPlain { Param( [Parameter(Mandatory=$true)] [System.Security.SecureString] $SecurePassword ) $passwordPointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword) $plainTextPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto($passwordPointer) [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($passwordPointer) return $plainTextPassword } 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 ($Script:isNano) { return $false } # Skip if Nano 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 } } Function Install-PoshSSHModule { if ($Script:isNano) { return $false } # Skip if Nano if ($InstallPoshSSHModule) { return $true } $title = 'Install-Posh-SSH_Module' $message = 'Do you want to install Posh-SSH_Module in-order to manage [Linux and VMware_ESXi Remote Host Systems] or not ?' $yes = New-Object System.Management.Automation.Host.ChoiceDescription '&Yes', 'install Posh-SSH_Module.' $no = New-Object System.Management.Automation.Host.ChoiceDescription '&No', 'Does not install Posh-SSH_Module.' $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 Install-RESTServerPrompt { if ($Script:isNano) { return $false } # Skip if Nano if ($InstallRESTServer) { return $true } $title = 'Install-REST Server' $message = 'Do you want to install the REST server to be able to invoke PowerShell cmdlets via REST?' $yes = New-Object System.Management.Automation.Host.ChoiceDescription '&Yes', 'Installs the REST server.' $no = New-Object System.Management.Automation.Host.ChoiceDescription '&No', 'Does not install the REST server.' $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-RESTServerProtocolPrompt { $defaultValue = 'http' while ($true) { $response = Read-Host -Prompt "Would you like to use http or https? [$defaultValue] ?" if ([string]::IsNullOrWhiteSpace($response)) { return $defaultValue } elseif ($response -ne 'http' -and $response -ne 'https' -and $response -ne 'both') { Out-Log -Data "Response must be either: 'http' or 'https'; please try again.`n" -ForegroundColor Red -Level 0 } else { return $response } } } Function Get-RESTServerPortPrompt { Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Protocol ) $defaultValue = '7777' if ($Protocol -eq 'https') { $defaultValue = '7778' } while ($true) { $response = Read-Host -Prompt "What $($Protocol.ToUpper()) port would you like to use for the REST server [$defaultValue] ?" if ([string]::IsNullOrWhiteSpace($response)) { return $defaultValue } $responseAsInt = $response -as [int] if ($responseAsInt -eq $null -or $responseAsInt -lt 1 -or $responseAsInt -gt 65535) { Out-Log -Data "Response must be a valid port number; please try again.`n" -ForegroundColor Red -Level 0 } else { return $response } } return $response } Function Get-RESTServerExistingHTTPSCert { $title = 'REST Server HTTPS Certificate Preference' $message = "Would you like to create a self-signed HTTPS certificate?`nSelecting no will allow you to choose the location of your existing HTTPS certificate." $yes = New-Object System.Management.Automation.Host.ChoiceDescription '&Yes', 'Creates a self-signed HTTPS certificate.' $no = New-Object System.Management.Automation.Host.ChoiceDescription '&No', 'Opens file selection dialog for location of existing HTTPS certificate.' $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no) while ($true) { $tempResult = $host.ui.PromptForChoice($title, $message, $options, 0) if ($tempResult -eq 0) { return $null # create self-signed cert } $fileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property @{ Title = 'Select HTTPS certificate to use for the REST server' InitialDirectory = (Get-Location).Path Filter = 'Certificate File (*.pfx)|*.pfx' Multiselect = $false } $null = $fileBrowser.ShowDialog() if (-not [string]::IsNullOrWhiteSpace($fileBrowser.FileName)) { return $fileBrowser.FileName } } } Function Get-RESTServerHTTPSCertPasswordPrompt { while ($true) { $password1 = Read-Host -AsSecureString 'Please enter a password for the HTTPS certificate' $password2 = Read-Host -AsSecureString 'Please re-enter the password for the HTTPS certificate' $password1Text = ConvertFrom-SecureToPlain -SecurePassword $password1 $password2Text = ConvertFrom-SecureToPlain -SecurePassword $password2 if ([string]::IsNullOrWhiteSpace($password1Text) -or [string]::IsNullOrWhiteSpace($password2Text)) { Out-Log -Data "One or both passwords are empty; please try again.`n" -ForegroundColor Red -Level 0 } elseif ($password1Text -ne $password2Text) { Out-Log -Data "Passwords do not match; please try again.`n" -ForegroundColor Red -Level 0 } else { return $password1 } } } Function Get-RESTServerHTTPSCertPasswordPromptSimple { $password = Read-Host -AsSecureString 'Please enter the password for the HTTPS certificate' return $password } Function Test-HTTPSCertPassword { Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $CertificateFilePath, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $SecurePassword ) $passwordText = ConvertFrom-SecureToPlain -SecurePassword $SecurePassword $null = certutil.exe -p $passwordText -dump $CertificateFilePath 2>&1 return $LASTEXITCODE -eq 0 } 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 Exit } } # 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. if ($Script:isNano) { return } # Skip if Nano if ($Script:isServerCore) { return } # Skip if Server Core # 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 Install-ForServerCore { Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $AppxFileName ) $serverCoreInstallPath = "$env:ProgramFiles\QLogic Corporation\PowerKit" $appxFileNameSimple = $AppxFileName.Replace('.\', '') $appxZipFileName = $appxFileNameSimple.Replace('.appx', '.zip') # Create installation directory if doesn't exist and copy Appx file to it (renamed to .zip) if (-not (Test-Path $serverCoreInstallPath)) { New-Item -Path $serverCoreInstallPath -Type Directory Copy-Item -Path $AppxFileName -Destination "$serverCoreInstallPath\$appxZipFileName" } # Unzip appx file to PowerKit directory Expand-Archive -Path "$serverCoreInstallPath\$appxZipFileName" -DestinationPath $serverCoreInstallPath # Register CIMProvider Register-CimProvider.exe -Namespace Root/qlgcfastlinq -ProviderName QLGCProvider -Path "$serverCoreInstallPath\QLGCProvider.dll" -verbose -ForceUpdate -HostingModel LocalSystemHost } 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/qlgcfastlinq -ProviderName QLGCProvider -Path QLGCProvider.dll -verbose -ForceUpdate -HostingModel LocalSystemHost $appxPath = (Get-AppxPackageWrapper -AppxName 'QLGCProvider').InstallLocation $mofcompOutput = & mofcomp.exe -N:root\qlgcfastlinq $appxPath\QLGCProvider.mof 2>&1 if ($mofcompOutput -match 'Error') { Out-Log -Data "Failed to register '$appxPath\QLGCProvider.mof': $($mofcompOutput -match 'Error')" -ForegroundColor Red -Level 1 } Add-ManyToRegistryWriteOver -Path "HKLM:\Software\Classes\CLSID\$Script:CLSID" -NameTypeValueArray @( @('(Default)', 'String', 'QLGCProvider') ) Add-ManyToRegistryWriteOver -Path "HKLM:\Software\Classes\CLSID\$Script:CLSID\InprocServer32" -NameTypeValueArray @( @('(Default)', 'String', "$appxPath\qlgcProvider.dll"), @('ThreadingModel', 'String', 'Free') ) } Function Remove-AppxTrustedAppsRegistryKey { if ($Script:isNano) { return } # Skip if Nano if ($Script:isServerCore) { return } # Skip if Server Core $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('QLGCProvider_') }).FullName foreach ($oldPath in $oldPaths) { Remove-Item $oldPath -Recurse -Force } # Copy cmdlets to ProgramData $appxPkg = Get-AppxPackageWrapper -AppxName 'QLGCProvider' $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 cmdlets." } } Function Copy-XMLTemplatesToProgramData { # Copy cmdlets to ProgramData $appxPkg = Get-AppxPackageWrapper -AppxName 'QLGCProvider' $xmlsTemplatesPath = $appxPkg.InstallLocation + '\Cmdlets\XML' if (Test-Path $xmlsTemplatesPath) { $xmlsTemplatesDestPath = "C:\Program Files\Marvell\PowerKit\XML" if (Test-Path $xmlsTemplatesDestPath){ Remove-Item $xmlsTemplatesDestPath -Recurse -Force } Invoke-IOWrapper -Expression "Copy-Item -Path `"$xmlsTemplatesPath`" -Destination `"$xmlsTemplatesDestPath`" -Recurse -Force" ` -SuccessMessage "XML Templates copied to '$xmlsTemplatesDestPath'." ` -FailMessage "Failed to copy XML Templates." } } Function Add-CmdletsPathToPSModulePath { # This function adds cmdlet module path to system PSModulePath environment variable permanently. 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 old paths with QLGCProvider from PSModulePath foreach ($path in $currPSModulePathArr) { if (-not $path.Contains('QLGCProvider')) { $newPSModulePathArr += $path } } # Add new cmdlet path to PSModulePath $newPSModulePathArr += $appxCmdletsPath # Skip for uninstall script $newPSModulePathStr = $newPSModulePathArr -join ';' # 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 Test-RESTAPI { Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Url, [Parameter(Mandatory=$false)] [AllowNull()] $EncodedAuthString ) try { if ($encodedAuthString -eq $null) { $response = (Invoke-RestMethod -Method Get -Uri $Url).value } else { $response = (Invoke-RestMethod -Method Get -Headers @{'Authorization' = "Basic $EncodedAuthString"} -Uri $Url).value } $response = ($response | Out-String).Trim() Out-Log -Data "`n$response`n" -Level 2 } 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 QLGC 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 QLGC 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('QLGC_') }).Name foreach ($moduleName in $moduleNames) { try { Import-Module $moduleName -Force 2>&1 | Out-Null } catch { # ignore any exception when importing. ErrorAction and 2>&1 | Out-Null in above command does not work! } } } #-------------------------------------------------------------------------------------------------- # Globals $Script:CLSID = '{BC33E6C0-DB6A-4781-B924-CB1CDA88E4A1}' $Script:isNano = Test-IfNano $Script:isServerCore = Test-IfServerCore $Script:savedAppxPolicyRegValData = $null $cmdletsAppxFileName = Get-CmdletsAppxFileName $Script:appcmd = "$env:SystemRoot\system32\inetsrv\appcmd.exe" $Script:netsh = "$env:windir\system32\netsh.exe" $Script:restServerZipFile = "$((Get-Location).path)\FastLinqProviderREST-WindowsServer.qlgc" $Script:Verbose = 1 Add-Type -AssemblyName System.IO.Compression.FileSystem Add-Type @" using System.Net; using System.Security.Cryptography.X509Certificates; public class TrustAllCertsPolicy : ICertificatePolicy { public bool CheckValidationResult( ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { return true; } } "@ [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Test-ExecutionPolicy if (-not $InstallRESTServerOnly) { # Check if PowerKit is already installed before proceeding if ($Script:isServerCore) { if (Test-Path -Path "$env:ProgramFiles\QLogic Corporation\PowerKit\Cmdlets") { Test-LastExitCode -ExitCode 1 -Message "Could not install PowerKit because it is already installed! Please uninstall the PowerKit first." -ExitScriptIfBadErrorCode } } else { if ((Get-AppxPackage -Name 'QLGCProvider') -ne $null) { Test-LastExitCode -ExitCode 1 -Message "Could not install PowerKit because it is already installed! Please uninstall the PowerKit first." -ExitScriptIfBadErrorCode } } #-------------------------------------------------------------------------------------------------- # User Input $response0 = Set-WinRMConfigurationPrompt $response1 = Install-PoshSSHModule $response2 = Install-RESTServerPrompt #-------------------------------------------------------------------------------------------------- # Script - Cmdlets $Script:CurrentInstallation = 'FastLinq PowerKit' if ($Script:isServerCore) { Install-ForServerCore -AppxFileName $cmdletsAppxFileName Add-CmdletsPathToPSModulePath } else { Add-AppxTrustedAppsRegistryKey Remove-CLSIDRegistryPaths Add-AppxPackageWrapper -AppxFileName $cmdletsAppxFileName Add-CLSIDRegistryPathsAndMofCompInstall Remove-AppxTrustedAppsRegistryKey Copy-CmdletsToProgramData Add-CmdletsPathToPSModulePath Copy-XMLTemplatesToProgramData } Set-WinRMConfiguration -SetWinRMConfigurationResponse $response0 Out-Log -Data "Successfully installed FastLinq PowerKit.`n" -ForegroundColor Green -Level 0 #-------------------------------------------------------------------------------------------------- # Script - Install-PoshSSH Module if ($response1) { $Script:CurrentInstallation = 'Posh-SSH Module' try { $PoshSSHInstalled = Install-Module -Name Posh-SSH -Force [object []] $Is_Posh_Instaled = Find-Module -Name Posh-SSH } catch { write-host "An error occurred while installing Posh-SSH Module !!!" -ForegroundColor Yellow } finally { if (($Is_Posh_Instaled.Count -gt 0) -and ($Is_Posh_Instaled.Name -imatch "Posh-SSH")) { write-host "Successfully installed Posh-SSH Module from PS Gallery." -ForegroundColor Green } else { write-host "Failed to install Posh-SSH Module from PS Gallery !!!" -ForegroundColor Red } } } #-------------------------------------------------------------------------------------------------- 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 } #-------------------------------------------------------------------------------------------------- # Script - REST API if ($response2 -or $InstallRESTServerOnly) { $Script:CurrentInstallation = 'FastLinq PowerKit REST Server' # Test-PowerShellVersion # Define REST server variables $restServerRegistryPath = 'HKLM:\Software\QLogic Corporation\PowerShell Cmdlets REST Server' $restServerInstallPath = "$env:ProgramFiles\QLogic Corporation\PowerShell Cmdlets REST Server" $restServerServiceName = 'QLogic PowerShell Cmdlets REST Server' # Check if REST server is already installed before proceeding if (Test-Path $restServerRegistryPath) { Test-LastExitCode -ExitCode 1 -Message "Could not install REST server because it is already installed! Please uninstall the REST server first." -ExitScriptIfBadErrorCode } # Confirm REST server installation file exists before proceeding if (-not (Test-Path -Path $Script:restServerZipFile)) { Test-LastExitCode -ExitCode 1 -Message "Could not install REST server because REST server installation file is missing." -ExitScriptIfBadErrorCode } Out-Log -Data "`nBeginning installation of $Script:CurrentInstallation" -ForegroundColor Cyan -Level 0 # Check if running inside a container $runningInContainer = $env:USERNAME -eq 'ContainerAdministrator' -and $env:USERDOMAIN -eq 'User Manager' # Determine protocol if (-not [String]::IsNullOrEmpty($RESTServerProtocol)) { $restServerProtocol = $RESTServerProtocol.ToLower() } else { $restServerProtocol = Get-RESTServerProtocolPrompt } # Determine port $ports = [ordered]@{} if ($RESTServerPort -ne 0) { $ports[$restServerProtocol] = $RESTServerPort } else { if ($restServerProtocol -eq 'http' -or $restServerProtocol -eq 'both') { $ports['http'] = Get-RESTServerPortPrompt -Protocol 'http' } if ($restServerProtocol -eq 'https' -or $restServerProtocol -eq 'both') { $ports['https'] = Get-RESTServerPortPrompt -Protocol 'https' } } # Get credential object if ($Credential -eq $null) { $username = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name $cred = Get-Credential -Message 'Enter Windows credentials to use for the REST server' -UserName $username } else { $cred = $Credential } $plainPassword = ConvertFrom-SecureToPlain -SecurePassword $cred.Password # Create install directory if doesn't exist and unzip files to it Out-Log -Data "`nCreating installation directory and extracting files" -ForegroundColor Cyan -Level 0 if (-not (Test-Path $restServerInstallPath)) { Out-Log -Data (New-Item -ItemType Directory -Path $restServerInstallPath -Force) -Level 2 } [System.IO.Compression.ZipFile]::ExtractToDirectory($restServerZipFile, $restServerInstallPath) # Add Windows registry entries Out-Log -Data "`nAdding Windows registry entries" -ForegroundColor Cyan -Level 0 Add-ManyToRegistryWriteOver -Path $restServerRegistryPath -NameTypeValueArray @( @('container', 'DWord', $runningInContainer), @('path', 'String', $restServerInstallPath), @('protocol', 'String', $restServerProtocol), @('http_port', 'DWord', $ports.http), @('https_port', 'DWord', $ports.https) ) if ($restServerProtocol -eq 'http' -or $restServerProtocol -eq 'both') { # Register URL with Windows Out-Log -Data "`nRegistering HTTP URL with Windows" -ForegroundColor Cyan -Level 0 Out-Log -Data (netsh.exe http add urlacl url=http://localhost:$($ports.http)/ user=\Everyone) -Level 2 Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to register HTTP URL with Windows; REST API may not operate correctly." } if ($restServerProtocol -eq 'https' -or $restServerProtocol -eq 'both') { # Prompt user for existing HTTPS certificate (will be null if user selects to use self-signed certificate) if ($CreateSelfSignedHTTPSCert -eq $true) { $existingCertPath = $null } else { $existingCertPath = Get-RESTServerExistingHTTPSCert } # Create self-signed certificate if ($existingCertPath -eq $null) { Out-Log -Data "`nCreating and importing HTTPS self-signed certificate" -ForegroundColor Cyan -Level 0 # Generate random filename for certificate file $certPath = "$($(New-Guid).Guid).pfx" # Prompt user for HTTPS certificate password if ($HTTPSCertPassword -ne $null) { $password = $HTTPSCertPassword } else { $password = Get-RESTServerHTTPSCertPasswordPrompt } # Create HTTPS certificate $thumb = (New-SelfSignedCertificate -CertStoreLocation cert:\localmachine\my -DnsName 'localhost').Thumbprint Out-Log -Data (Export-PfxCertificate -cert "cert:\localmachine\my\$thumb" -FilePath $certPath -Password $password) -Level 2 Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to create HTTPS certificate" -ExitScriptIfBadErrorCode # Import HTTPS certificate $passwordText = ConvertFrom-SecureToPlain -SecurePassword $password Out-Log -Data (certutil.exe -p $passwordText -importpfx $certPath) -Level 2 Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to import the HTTPS certificate" -ExitScriptIfBadErrorCode } # Use existing certificate else { Out-Log -Data "`nImporting HTTPS certificate" -ForegroundColor Cyan -Level 0 while ($true) { # Prompt user for HTTPS certificate password if ($HTTPSCertPassword -ne $null) { $password = $HTTPSCertPassword } else { $password = Get-RESTServerHTTPSCertPasswordPromptSimple } # Verify password is correct $passwordIsCorrect = Test-HTTPSCertPassword -CertificateFilePath $existingCertPath -SecurePassword $password if ($passwordIsCorrect) { break; } Out-Log -Data "`nPassword was incorrect; please try again." -Level 0 } # Import HTTPS certificate $passwordText = ConvertFrom-SecureToPlain -SecurePassword $password Out-Log -Data (certutil.exe -p $passwordText -importpfx $existingCertPath) -Level 2 Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to import the HTTPS certificate" # Get thumbprint $dumpOutput = certutil.exe -p $passwordText -dump $existingCertPath 2>&1 $line = $dumpOutput | Where-Object {$_ -match 'Cert Hash'} $thumb = $line.Split(':')[1].replace(' ', '') } # Register URL with Windows Out-Log -Data "`nRegistering HTTPS URL with Windows" -ForegroundColor Cyan -Level 0 Out-Log -Data (netsh.exe http add urlacl url=https://localhost:$($ports.https)/ user=\Everyone) -Level 2 Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to register HTTPS URL with Windows; REST API may not operate correctly." $uuid = "{$($(New-Guid).Guid)}" Out-Log -Data (netsh.exe http add sslcert ipport=0.0.0.0:$($ports.https) certhash=$thumb appid=$uuid) -Level 2 # Store HTTPS thumbprint in Windows registry Out-Log -Data "`nAdding Windows registry entry" -ForegroundColor Cyan -Level 0 Add-ManyToRegistryWriteOver -Path $restServerRegistryPath -NameTypeValueArray @( @('thumbprint', 'String', $thumb) ) # Delete cert.pfx file if ($existingCertPath -eq $null -and $certPath -ne $null) { Remove-Item -Path $certPath -Force } } # Add new firewall rule(s) if (-not $runningInContainer) { foreach ($protocol in $ports.Keys) { $port = $ports.Item($protocol) if ($port -ne $null) { Out-Log -Data (New-NetFirewallRule -DisplayName "$restServerServiceName - $($protocol.ToUpper())" -Direction Inbound -Action Allow -Protocol TCP -LocalPort $port) -Level 3 } } } # Install as a Windows service Out-Log -Data "`nInstalling Windows service" -ForegroundColor Cyan -Level 0 Out-Log -Data (& "$restServerInstallPath\PowerShellCmdletsRestServer.exe" -install) -Level 2 Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to install Windows service" -ExitScriptIfBadErrorCode # Wait for service to start before continuing Out-Log -Data "`nWaiting for service to start before continuing." -ForegroundColor Cyan -Level 0 $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() $timeoutSecs = 60 while ($true) { Start-Sleep -Milliseconds 2500 $service = Get-Service -Name $restServerServiceName if ($service -ne $null -and $service.Status -eq 'Running') { Out-Log -Data '... service started!' -ForegroundColor Cyan -Level 0 break; } if ($stopwatch.Elapsed.TotalSeconds -gt $timeoutSecs) { Test-LastExitCode -ExitCode 1 -Message "`n$restServerServiceName service never started after $timeoutSecs seconds; giving up." -ExitScriptIfBadErrorCode } Out-Log -Data 'Still waiting for service to start ...' -ForegroundColor Cyan -Level 0 } # # Change running user of Windows service # Out-Log -Data "`nChanging running user of Windows service" -ForegroundColor Cyan -Level 0 # $filter = "Name='QLogic PowerShell Cmdlets REST Server'" # $service = Get-WMIObject -namespace "root\cimv2" -class Win32_Service -Filter $filter # Out-Log -Data ($service.Change($null, $null, $null, $null, $null, $null, $cred.UserName, $plainPassword)) -Level 2 # Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to change running user of Windows service; REST API may not operate correctly." # Out-Log -Data ($service.StopService()) -Level 2 # Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to stop Windows service" -ExitScriptIfBadErrorCode # while ($service.Started) { # Out-Log -Data 'Waiting for service to stop ...' -Level 2 # Start-Sleep -Milliseconds 1000 # $service = Get-WMIObject -namespace "root\cimv2" -class Win32_Service -Filter $filter # } # Out-Log -Data $service.StartService() -Level 2 # Test-LastExitCode -ExitCode $LASTEXITCODE -Message "Failed to start Windows service" -ExitScriptIfBadErrorCode # Get base64 credential to use for invoking REST $encodedAuthString = Convert-CredentialToHeaderAuthorizationString -Credential $cred Out-Log -Data "`nUse this string in the REST API request header:" -Level 0 Out-Log -Data " Authorization: Basic $encodedAuthString" -Level 0 -ForegroundColor Gray foreach ($protocol in $ports.Keys) { $port = $ports.Item($protocol) if ($port -ne $null) { # Test REST endpoint #1 $url = "$protocol`://localhost:$port/ping" Out-Log -Data "`nTesting $($protocol.ToUpper()) REST API with $url (no authentication required)" -ForegroundColor Cyan -Level 0 Test-RESTAPI -Url $url # Test REST endpoint #2 $url = "$protocol`://localhost:$port/pingAuth" Out-Log -Data "Testing $($protocol.ToUpper()) REST API with $url (requires authentication)" -ForegroundColor Cyan -Level 0 Test-RESTAPI -Url $url -EncodedAuthString $encodedAuthString } } Out-Log -Data "`nSuccessfully installed FastLinq PowerKit REST Server." -ForegroundColor Green -Level 0 } |