PureStoragePowerShellToolkit.WindowsAdministration.psm1
<#
=========================================================================== Release version: 3.0.1 Revision information: Refer to the changelog.md file --------------------------------------------------------------------------- Maintained by: FlashArray Integrations and Evangelsigm Team @ Pure Storage Organization: Pure Storage, Inc. Filename: PureStoragePowerShellToolkit.WindowsAdministration.psm1 Copyright: (c) 2023 Pure Storage, Inc. Module Name: PureStoragePowerShellToolkit.WindowsAdministration Description: PowerShell Script Module (.psm1) -------------------------------------------------------------------------- Disclaimer: The sample module and documentation are provided AS IS and are not supported by the author or the author’s employer, unless otherwise agreed in writing. You bear all risk relating to the use or performance of the sample script and documentation. The author and the author’s employer disclaim all express or implied warranties (including, without limitation, any warranties of merchantability, title, infringement or fitness for a particular purpose). In no event shall the author, the author’s employer or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever arising out of the use or performance of the sample script and documentation (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss), even if such person has been advised of the possibility of such damages. -------------------------------------------------------------------------- Contributors: Rob "Barkz" Barker @purestorage, Robert "Q" Quimbey @purestorage, Mike "Chief" Nelson, Julian "Doctor" Cates, Marcel Dussil @purestorage - https://en.pureflash.blog/ , Craig Dayton - https://github.com/cadayton , Jake Daniels - https://github.com/JakeDennis, Richard Raymond - https://github.com/data-sciences-corporation/PureStorage , The dbatools Team - https://dbatools.io , many more Puritans, and all of the Pure Code community who provide excellent advice, feedback, & scripts now and in the future. =========================================================================== #> #Requires -Version 5 #Requires -Modules @{ ModuleName='PureStoragePowerShellToolkit.FlashArray'; ModuleVersion='3.0.1' } Import-Module 'CimCmdlets' -Global Import-Module 'Storage' -Global #region Helper functions function Convert-UnitOfSize { [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [AllowNull()] $Value, $To = 1GB, $From = 1, $Decimals = 2 ) process { return [math]::Round($Value * $From / $To, $Decimals) } } function Write-Color { [CmdletBinding()] param( [Parameter(Position = 0, Mandatory, ValueFromPipeline)] [string[]] $Text, [ConsoleColor[]] $ForegroundColor = ([console]::ForegroundColor), [ConsoleColor[]] $BackgroundColor = ([console]::BackgroundColor), [int] $Indent = 0, [int] $LeadingSpace = 0, [int] $TrailingSpace = 0, [switch] $NoNewLine ) begin { $baseParams = @{ ForegroundColor = [console]::ForegroundColor BackgroundColor = [console]::BackgroundColor NoNewline = $true } # Add leading lines Write-Host ("`n" * $LeadingSpace) @baseParams } process { # Add TABs before text Write-Host ("`t" * $Indent) @baseParams if ($PSBoundParameters.ContainsKey('ForegroundColor') -or $PSBoundParameters.ContainsKey('BackgroundColor')) { $writeParams = $baseParams.Clone() for ($i = 0; $i -lt $Text.Count; $i++) { if ($i -lt $ForegroundColor.Count) { $writeParams['ForegroundColor'] = $ForegroundColor[$i] } if ($i -lt $BackgroundColor.Count) { $writeParams['BackgroundColor'] = $BackgroundColor[$i] } Write-Host $Text[$i] @writeParams } } else { Write-Host $Text -NoNewline } if (-not $NoNewLine) { Write-Host } } end { if (-not $NoNewLine) { Write-Host ("`n" * $TrailingSpace) @baseParams } } } #endregion Helper functions function Get-Pfa2SerialNumbers() { <# .SYNOPSIS Retrieves FlashArray disk serial numbers connected to the host. .DESCRIPTION Cmdlet retrieves disk serial numbers that are associated to Pure FlashArrays. .PARAMETER CimSession Optional. A CimSession or computer name. CIM session may be reused. .INPUTS CimSession is optional. .OUTPUTS Outputs serial numbers of FlashArrays devices. .EXAMPLE Get-Pfa2SerialNumbers Returns serial number information on Pure FlashArray disk devices connected to the host. .EXAMPLE Get-Pfa2SerialNumbers -CimSession 'myComputer' Returns serial number information on Pure FlashArray disk devices connected to 'myComputer' with current credentials. .EXAMPLE $session = New-CimSession 'myComputer' -Credential (Get-Credential) Get-Pfa2SerialNumbers -CimSession $session Get-Pfa2HostBusAdapter -CimSession $session Returns serial number information on Pure FlashArray disk devices and host bus adapter with previously created CIM session. .EXAMPLE Get-Pfa2SerialNumbers -CimSession (New-CimSession 'myComputer' -Credential $Creds) Returns serial number information on Pure FlashArray disk devices connected to 'myComputer' with credentials stored in variable $Creds. .EXAMPLE Get-Pfa2SerialNumbers -CimSession (New-CimSession 'myComputer' -Credential (Get-Secret admin)) Returns serial number information on Pure FlashArray disk devices connected to 'myComputer' with credentials stored in a secret vault. .EXAMPLE Get-Pfa2SerialNumbers -CimSession (New-CimSession 'myComputer' -Credential (Get-Credential)) Returns serial number information on Pure FlashArray disk devices connected to 'myComputer'. Asks for credentials. .EXAMPLE 'myComputer' | Get-Pfa2SerialNumbers Returns serial number information on Pure FlashArray disk devices connected to 'myComputer' with current credentials. .EXAMPLE $session = New-CimSession 'myComputer' -Credential (Get-Credential) $session | Get-Pfa2SerialNumbers Returns serial number information on Pure FlashArray disk devices with previously created CIM session. .EXAMPLE 'myComputer01', 'myComputer02' | Get-Pfa2SerialNumbers Returns serial number information on Pure FlashArray disk devices connected to 'myComputer01' and 'myComputer02' with current credentials. .EXAMPLE $prod = [pscustomobject]@{Caption = 'Prod Server'; CimSession = 'myComputer'} $prod | Get-Pfa2SerialNumbers Returns serial number information on Pure FlashArray disk devices connected to 'myComputer' with current credentials. #> [CmdletBinding()] Param ( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNull()] [CimSession]$CimSession ) process { Get-Disk -FriendlyName 'PURE FlashArray*' @PSBoundParameters | Select-Object PSComputerName, Number, SerialNumber } } function Get-Pfa2HostBusAdapter() { <# .SYNOPSIS Retrieves host bus adapater (HBA) information. .DESCRIPTION Retrieves host bus adapater (HBA) information for the host. .PARAMETER CimSession Optional. A CimSession or computer name. .INPUTS CimSession is optional. .OUTPUTS Host bus adapater information. .EXAMPLE Get-Pfa2HostBusAdapter Returns HBA information for the host. .EXAMPLE Get-Pfa2HostBusAdapter -CimSession 'myComputer' Returns HBA information for 'myComputer' with current credentials. .EXAMPLE $session = New-CimSession 'myComputer' -Credential (Get-Credential) Get-Pfa2SerialNumbers -CimSession $session Get-Pfa2HostBusAdapter -CimSession $session Returns serial number information on Pure FlashArray disk devices and host bus adapter with previously created CIM session. .EXAMPLE Get-Pfa2HostBusAdapter -CimSession (New-CimSession 'myComputer' -Credential $Creds) Returns HBA information for 'myComputer' with credentials stored in variable $Creds. .EXAMPLE Get-Pfa2HostBusAdapter -CimSession (New-CimSession 'myComputer' -Credential (Get-Secret admin)) Returns HBA information for 'myComputer' with credentials stored in a secret vault. .EXAMPLE Get-Pfa2HostBusAdapter -CimSession (New-CimSession 'myComputer' -Credential (Get-Credential)) Returns HBA information for 'myComputer'. Asks for credentials. .EXAMPLE 'myComputer' | Get-Pfa2HostBusAdapter Returns HBA information for 'myComputer' with current credentials. .EXAMPLE $session = New-CimSession 'myComputer' -Credential (Get-Credential) $session | Get-Pfa2HostBusAdapter Returns HBA information for 'myComputer' with previously created CIM session. .EXAMPLE 'myComputer01', 'myComputer02' | Get-Pfa2HostBusAdapter Returns HBA information for 'myComputer01' and 'myComputer02' with current credentials. .EXAMPLE $prod = [pscustomobject]@{Caption = 'Prod Server'; CimSession = 'myComputer'} $prod | Get-Pfa2HostBusAdapter Returns HBA information for 'myComputer' with current credentials. #> [CmdletBinding()] Param ( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNull()] [CimSession]$CimSession ) process { function ConvertTo-HexAndColons([byte[]]$address) { return (($address | ForEach-Object { '{0:x2}' -f $_ }) -join ':').ToUpper() } try { $ports = Get-CimInstance -Class 'MSFC_FibrePortHBAAttributes' -Namespace 'root\WMI' @PSBoundParameters -ea Stop $adapters = Get-CimInstance -Class 'MSFC_FCAdapterHBAAttributes' -Namespace 'root\WMI' @PSBoundParameters -ea Stop foreach ($adapter in $adapters) { $attributes = $ports.Where({ $_.InstanceName -eq $adapter.InstanceName }, 'first').Attributes $adapter | Select-Object -ExcludeProperty 'NodeWWN', 'Cim*' -Property *, @{n = 'NodeWWN'; e = { ConvertTo-HexAndColons $_.NodeWWN } }, @{n = 'FabricName'; e = { ConvertTo-HexAndColons $attributes.FabricName } }, @{n = 'PortWWN'; e = { ConvertTo-HexAndColons $attributes.PortWWN } } } } catch [Microsoft.Management.Infrastructure.CimException] { if ($_.Exception.NativeErrorCode -ne 'NotSupported') { throw } } } } function Get-Pfa2MPIODiskLBPolicy() { <# .SYNOPSIS Retrieves the current MPIO Load Balancing policy for Pure FlashArray disk(s). .DESCRIPTION This cmdlet will retrieve the current MPIO Load Balancing policy for connected Pure FlashArrays disk(s) using the mpclaim.exe utlity. .PARAMETER DiskId Optional. If specified, retrieves only the policy for the that MPIO disk. Otherwise, returns all disks. .INPUTS Disk number is optional. .OUTPUTS mpclaim.exe output. .EXAMPLE Get-Pfa2MPIODiskLBPolicy Returns the current MPIO Load Balancing Policy for all MPIO disks. .EXAMPLE Get-Pfa2MPIODiskLBPolicy -DiskId 1 Returns the current MPIO LB policy for MPIO disk 1. .EXAMPLE 2, 3 | Get-Pfa2MPIODiskLBPolicy Returns the current MPIO LB policy for MPIO disks 2 and 3. .EXAMPLE $dataDisk = [pscustomobject]@{Caption = 'Prod Data'; DiskId = 2} $dataDisk | Get-Pfa2MPIODiskLBPolicy Returns the current MPIO LB policy for MPIO disk 2. #> [CmdletBinding()] Param ( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateRange(0, [int]::MaxValue)] [int]$DiskId ) process { #Checks whether mpclaim.exe is available. $exists = Test-Path "$env:systemroot\System32\mpclaim.exe" if (-not ($exists)) { Write-Error 'mpclaim.exe not found. Is MultiPathIO enabled? Exiting.' -ErrorAction Stop } $expr = 'mpclaim.exe -s -d ' if ($PSBoundParameters.ContainsKey('DiskId')) { Write-Host "Getting current MPIO Load Balancing Policy for MPIO disk $DiskId" -ForegroundColor Green $expr += $DiskId } else { Write-Host 'Getting current MPIO Load Balancing Policy for all MPIO disks.' -ForegroundColor Green } Invoke-Expression $expr } } function Get-Pfa2QuickFixEngineering() { <# .SYNOPSIS Retrieves all the Windows OS QFE patches applied. .DESCRIPTION Retrieves all the Windows OS QFE patches applied. .PARAMETER CimSession Optional. A CimSession or computer name. .INPUTS CimSession is optional. .OUTPUTS Outputs a listing of QFE patches applied. .EXAMPLE Get-Pfa2QuickFixEngineering Retrieves all the Windows OS QFE patches applied. .EXAMPLE Get-Pfa2QuickFixEngineering -CimSession 'myComputer' Retrieves all the Windows OS QFE patches applied to 'myConputer' with current credentials. .EXAMPLE $session = New-CimSession 'myComputer' -Credential (Get-Credential) Get-Pfa2QuickFixEngineering -CimSession $session Get-Pfa2HostBusAdapter -CimSession $session Retrieves all the Windows OS QFE patches applied and host bus adapter with previously created CIM session. .EXAMPLE Get-Pfa2QuickFixEngineering -CimSession (New-CimSession 'myComputer' -Credential $Creds) Retrieves all the Windows OS QFE patches applied to 'myComputer' with credentials stored in variable $Creds. .EXAMPLE Get-Pfa2QuickFixEngineering -CimSession (New-CimSession 'myComputer' -Credential (Get-Secret admin)) Retrieves all the Windows OS QFE patches applied to 'myComputer' with credentials stored in a secret vault. .EXAMPLE Get-Pfa2QuickFixEngineering -CimSession (New-CimSession 'myComputer' -Credential (Get-Credential)) Retrieves all the Windows OS QFE patches applied to 'myComputer'. Asks for credentials. .EXAMPLE 'myComputer' | Get-Pfa2QuickFixEngineering Retrieves all the Windows OS QFE patches applied to 'myComputer' with current credentials. .EXAMPLE $session = New-CimSession 'myComputer' -Credential (Get-Credential) $session | Get-Pfa2QuickFixEngineering Retrieves all the Windows OS QFE patches applied to 'myComputer' with previously created CIM session. .EXAMPLE 'myComputer01', 'myComputer02' | Get-Pfa2QuickFixEngineering Retrieves all the Windows OS QFE patches applied to 'myComputer01' and 'myComputer02' with current credentials. .EXAMPLE $prod = [pscustomobject]@{Caption = 'Prod Server'; CimSession = 'myComputer'} $prod | Get-Pfa2QuickFixEngineering Retrieves all the Windows OS QFE patches applied to 'myComputer' with current credentials. #> [CmdletBinding()] Param ( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNull()] [CimSession]$CimSession ) process { Get-CimInstance -Class 'Win32_QuickFixEngineering' @PSBoundParameters | Select-Object PSComputerName, Description, HotFixID, InstalledOn } } function Get-Pfa2VolumeShadowCopy() { <# .SYNOPSIS Exposes volume shadow copy using the Diskshadow command. .DESCRIPTION This cmdlet will expose volume shadow copy using the Diskshadow command, passing the variables specified. .PARAMETER ExposeAs Required. Drive letter, share, or mount point to expose the shadow copy. .PARAMETER Alias Required. Name of the shadow copy alias. .PARAMETER MetadataFile Required. Filename for the metadata .cab file. .PARAMETER VerboseMode Optional. 'On' or 'Off'. If set to 'Off', verbose mode for the Diskshadow command is disabled. Default is 'On'. .INPUTS None .OUTPUTS diskshadow.exe output. .EXAMPLE Get-Pfa2VolumeShadowCopy -MetadataFile prodmeta.cab -Alias Prod -ExposeAs G: Exposes the Prod shadow copy as drive letter G: using the prodmeta.cab metadata file. .NOTES See https://docs.microsoft.com/windows-server/administration/windows-commands/diskshadow for more information on the Diskshadow utility. #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [ValidateNotNullorEmpty()] [string]$MetadataFile, [Parameter(Mandatory)] [ValidateNotNullorEmpty()] [string]$Alias, [Parameter(Mandatory)] [ValidateNotNullorEmpty()] [string]$ExposeAs, [ValidateSet('On', 'Off')] [string]$VerboseMode = 'On' ) $dsh = "./PUREVSS-SNAP.PFA" try { 'RESET', "SET VERBOSE $VerboseMode", "LOAD METADATA $MetadataFile", 'IMPORT', "EXPOSE %$Alias% $ExposeAs", 'EXIT' | Set-Content $dsh DISKSHADOW /s $dsh } finally { Remove-Item $dsh -ErrorAction SilentlyContinue } } function Get-Pfa2WindowsDiagnosticInfo() { <# .SYNOPSIS Gathers Windows operating system, hardware, and software information, including logs for diagnostics. This cmdlet requires Administrative permissions. .DESCRIPTION This script will collect detailed information on the Windows operating system, hardware and software components, and collect event logs in .evtx and .csv formats. It will optionally collect WSFC logs and optionally compress all gathered files intoa .zip file for easy distribution. This script will place all of the files in a parent folder that is named after the computer NetBios name($env:computername). Each section of information gathered will have it's own child folder in that parent folder. By default, the output will be placed in the %temp% folder. .PARAMETER Path Optional. Directory path for the output. If not specified, the output will be placed in the %temp% folder. .PARAMETER Cluster Optional. Collect Windows Server Failover Cluster (WSFC) logs. .PARAMETER Compress Optional. Compress the folder that contains all the gathered data into a zip file. The file name will be the computername_diagnostics.zip. .INPUTS None .OUTPUTS Diagnostic outputs in txt and event log files. Compressed zip file. .EXAMPLE Get-Pfa2WindowsDiagnosticInfo -Path '.\diagnostic_report' -Cluster Retrieves all of the operating system, hardware, software, event log, and WSFC logs into the 'diagnostic_report' folder. .EXAMPLE Get-Pfa2WindowsDiagnosticInfo -Cluster Retrieves all of the operating system, hardware, software, event log, and WSFC logs into the default folder. .EXAMPLE Get-Pfa2WindowsDiagnosticInfo -Compress Retrieves all of the operating system, hardware, software, event log, and compresses the parent folder into a zip file that will be created in the %temp% folder. .NOTES This cmdlet requires Administrative permissions. #> [cmdletbinding()] Param( [string]$Path = (Join-Path $env:Temp $env:computername), [switch]$Cluster, [switch]$Compress ) { # System Information {msinfo32 /report msinfo32.txt | Out-Null} | Get-Diagnostic -header 'system information' # Hotfixes { Get-HotFix | Format-Table -Wrap -AutoSize | Out-File 'Get-Hotfix.txt' } | Get-Diagnostic -header 'hotfix information' # Storage { # Disk { fsutil behavior query DisableDeleteNotify | Out-File 'fsutil_behavior_DisableDeleteNotify.txt' Get-PhysicalDisk | Format-List | Out-File 'Get-PhysicalDisk.txt' Get-Disk | Format-List | Out-File 'Get-Disk.txt' Get-Volume | Format-List | Out-File 'Get-Volume.txt' Get-Partition | Format-List | Out-File 'Get-Partition.txt' } | Get-Diagnostic -header 'disk information' # MPIO { if (Test-Path "$env:systemroot\System32\mpclaim.exe") { mpclaim -s -d | Out-File 'mpclaim_-s_-d.txt' mpclaim -v | Out-File 'mpclaim_-v.txt' } if (Get-Module -ListAvailable 'mpio') { Get-MPIOSetting | Out-File 'Get-MPIOSetting.txt' Get-MPIOAvailableHW | Out-File 'Get-MPIOAvailableHW.txt' Get-MSDSMGlobalDefaultLoadBalancePolicy | Out-File 'Get-MSDSMGlobalDefaultLoadBalancePolicy.txt' } $root = 'HKLM:\System\CurrentControlSet\Services' $keys = @( @{'service' = 'MSDSM'; 'key' = 'MSDSM\Parameters'; 'file' = 'Get-ItemProperty_msdsm.txt' }, @{'service' = 'mpio'; 'key' = 'mpio\Parameters'; 'file' = 'Get-ItemProperty_mpio.txt' }, @{'service' = 'Disk'; 'key' = 'Disk'; 'file' = 'Get-ItemProperty_disk.txt' } ) $keys | Where-Object { Join-Path $root $_.service | Test-Path } | ForEach-Object { Join-Path $root $_.key | Get-ItemProperty | Out-File $_.file } } | Get-Diagnostic -header 'MPIO information' # Fibre Channel { winrm e wmi/root/wmi/MSFC_FCAdapterHBAAttributes 2>&1 | Out-File 'MSFC_FCAdapterHBAAttributes.txt' winrm e wmi/root/wmi/MSFC_FibrePortHBAAttributes 2>&1 | Out-File 'MSFC_FibrePortHBAAttributes.txt' Get-InitiatorPort | Out-File 'Get-InitiatorPort.txt' } | Get-Diagnostic -header 'fibre channel information' } | Get-Diagnostic -header 'storage information' -location 'storage' # Network { Get-NetAdapter | Format-Table -AutoSize -Wrap | Out-File 'Get-NetAdapter.txt' Get-NetAdapterAdvancedProperty | Format-Table -AutoSize -Wrap | Out-File 'Get-NetAdapterAdvancedProperty.txt' } | Get-Diagnostic -header 'network information' -location 'network' # Event Logs { $logs = @('System', 'Setup', 'Security', 'Application') # Export { $logs | ForEach-Object { wevtutil epl $_ "$_.evtx" /ow } } | Get-Diagnostic -header 'event log files' # Locale-specific messages { $logs | ForEach-Object { wevtutil al "$_.evtx" } } | Get-Diagnostic -header 'locale-specific information' #Get critical, error, & warning events { $logs | ForEach-Object { Get-WinEvent -FilterHashtable @{LogName = $_; Level = 1, 2, 3 } -ea SilentlyContinue | Export-Csv "$_-CRITICAL.csv" -NoTypeInformation } } | Get-Diagnostic -header 'critical, error, & warning events' # Get informational events { $logs | ForEach-Object { Get-WinEvent -FilterHashtable @{LogName = $_; Level = 4 } -ea SilentlyContinue | Export-Csv "$_-INFO.csv" -NoTypeInformation } } | Get-Diagnostic -header 'informational events' } | Get-Diagnostic -header 'event log' -location 'log' # WSFC inforation If ($Cluster) { { Get-ClusterLog -Destination '.' | Out-Null Get-ClusterSharedVolume | Select-Object * | Out-File 'Get-ClusterSharedVolume.txt' Get-ClusterSharedVolumeState | Select-Object * | Out-File 'Get-ClusterSharedVolumeState.txt' } | Get-Diagnostic -header 'cluster information' -location 'cluster' } } | Get-Diagnostic -header 'diagnostic information' -location $Path -long # Compress If ($Compress) { $params = @{ Path = $Path CompressionLevel = 'Optimal' DestinationPath = $Path + '_diagnostics.zip' } Compress-Archive @params -Force } } function Get-Diagnostic() { param( [Parameter(ValueFromPipeline)] [scriptblock]$command, [string]$header = 'diagnostic', [string]$location, [switch]$long ) $message = "Retrieving $header" $message += if (-not $long) { '...' } else { '. This will take some time to complete. Please wait...' } Write-Host $message -ForegroundColor Yellow if ($location) { if (-not (Test-Path $location -PathType Container)) { New-Item $location -ItemType 'Directory' | Out-Null } Push-Location $location } try { Invoke-Command $command } finally { if ($location) { Pop-Location } Write-Host "Retrieving $header completed." -ForegroundColor Green } } function New-Pfa2HypervClusterVolumeReport() { <# .SYNOPSIS Creates a Excel report on volumes connected to a Hyper-V cluster. .DESCRIPTION This creates separate CSV files for VM, Windows Hosts, and FlashArray information for each endpoint specified that is part of a HyperV cluster. It then takes that output and places it into a an Excel workbook that contains sheets for each CSV file. .PARAMETER Endpoint Required. An IP address or FQDN of the FlashArray. Multiple endpoints can be specified. .PARAMETER VmCsvFileName Optional. Defaults to VMs.csv. .PARAMETER WinCsvFileName Optional. defaults to WindowsHosts.csv. .PARAMETER PfaCsvFileName Optional. defaults to FlashArrays.csv. .PARAMETER ExcelFile Optional. defaults to HypervClusterReport.xlsx. .INPUTS Endpoint is mandatory. VM, Win, and PFA csv file names are optional. .OUTPUTS Outputs individual CSV files and creates an Excel workbook that is built using the required PowerShell module ImportExcel, created by Douglas Finke. .EXAMPLE New-Pfa2HypervClusterVolumeReport -Endpoint myarray -VmCsvName myVMs.csv -WinCsvName myWinHosts.csv -PfaCsvName myFlashArray.csv -ExcelFile myExcelFile.xlsx This will create three separate CSV files with HyperV cluster information and incorporate them into a single Excel workbook. .EXAMPLE New-Pfa2HypervClusterVolumeReport -Endpoint 'myarray01', 'myarray02' This will create files with HyperV cluster information, and FlashArray information for myarray01, and myarray02. .EXAMPLE Get-Content '.\arrays.txt' | New-Pfa2HypervClusterVolumeReport This will create files with HyperV cluster information, and FlashArray information for each array in the arrays.txt file. .EXAMPLE New-Pfa2HypervClusterVolumeReport -Endpoint 'myarray.mydomain.com' -Credential (Get-Credential) This will create files with HyperV cluster information, and FlashArray information for myarray.mydomain.com. Asks for FlashArray credentials. .EXAMPLE $endpoint = [pscustomobject]@{Endpoint = @('myarray.mydomain.com'); Credential = (Get-Credential)} $endpoint | New-Pfa2HypervClusterVolumeReport This will create files with HyperV cluster information, and FlashArray information for myarray.mydomain.com. Asks for FlashArray credentials. .NOTES This cmdlet can utilize the global credential variable for FlashArray authentication. Set the credential variable by using the command Set-Pfa2Credential. #> [CmdletBinding()] Param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string[]]$Endpoint, [string]$VmCsvFileName = "VMs.csv", [string]$WinCsvFileName = "WindowsHosts.csv", [string]$PfaCsvFileName = "FlashArrays.csv", [string]$ExcelFile = "HypervClusterReport.xlsx", [Parameter(ValueFromPipelineByPropertyName)] [pscredential]$Credential = ( Get-Pfa2Credential ) ) begin { #Validate modules $modules = @('Hyper-V', 'FailoverClusters') foreach ($module in $modules) { if (-not (Get-Module -ListAvailable $module)) { Write-Error "Required module $module not found" return } } $report = [ordered]@{} #Get VMs & VHDs $nodes = Get-ClusterNode $vhds = Get-VM -ComputerName $nodes.Name | ForEach-Object { $_ } -PipelineVariable 'vm' | ForEach-Object { Get-Vhd -ComputerName $_.ComputerName -VmId $_.VmId } | ForEach-Object { [pscustomobject][ordered]@{ 'VM Name' = $vm.Name 'VM State' = $vm.State 'ComputerName' = $_.ComputerName 'Path' = $_.Path 'VHD Type' = $_.VhdType 'Size (GB)' = Convert-UnitOfSize $_.Size -To 1GB 'Size on disk (GB)' = Convert-UnitOfSize $_.FileSize -To 1GB } } if ($vhds) { $vhds | Export-Csv $VmCsvFileName -NoTypeInformation $report['VMs'] = $vhds } #Get hosts and volumes $volumes = $nodes | ForEach-Object { $_ } -PipelineVariable 'node' | ForEach-Object { Get-Disk -CimSession $node.Name | Where-Object Number -ne $null | Get-Partition | Get-Volume } | Where-Object DriveType -eq Fixed | ForEach-Object { [pscustomobject][ordered]@{ 'ComputerName' = $node.Name 'Label' = $_.FileSystemLabel 'Name' = if ($_.DriveLetter) { "$($_.DriveLetter):\" } else { $_.Path } 'Total size (GB)' = Convert-UnitOfSize $_.Size -To 1GB 'Free space (GB)' = Convert-UnitOfSize $_.SizeRemaining -To 1GB 'Size on disk (GB)' = Convert-UnitOfSize ($_.Size - $_.SizeRemaining) -To 1GB } } if ($volumes) { $volumes | Export-Csv $WinCsvFileName -NoTypeInformation $report['Windows Hosts'] = $volumes } #Get Pure volumes $sn = $vhds | ForEach-Object { Get-Volume -FilePath $_.Path -CimSession $_.ComputerName } | Group-Object 'ObjectId' | ForEach-Object { $_.Group[0] } | Get-Partition | Get-Disk | Select-Object -ExpandProperty 'SerialNumber' $pureVolumes = @() } process { #Run through each array foreach ($e in $Endpoint) { #Connect to FlashArray try { $flashArray = Connect-Pfa2Array -Endpoint $e -Credential $Credential -IgnoreCertificateError } catch { $ExceptionMessage = $_.Exception.Message Write-Error "Failed to connect to FlashArray endpoint $e with: $ExceptionMessage" return } #FlashArray volumes try { $details = Get-Pfa2Array -Array $flasharray $pureVolumes += Get-Pfa2Volume -Array $flashArray | Where-Object { $sn -contains $_.serial } | Select-Object 'Name' -ExpandProperty 'Space' | ForEach-Object { [pscustomobject][ordered]@{ 'Array' = $details.Name 'Name' = $_.Name 'Size (GB)' = Convert-UnitOfSize $_.TotalProvisioned -To 1GB 'Size on disk (GB)' = Convert-UnitOfSize $_.TotalPhysical -To 1GB 'Data Reduction' = [math]::round($_.DataReduction, 2) } } } finally { Disconnect-Pfa2Array -Array $flashArray } } } end { if ($pureVolumes) { $pureVolumes | Export-Csv $PfaCsvFileName -NoTypeInformation $report['FlashArrays'] = $pureVolumes } Export-Pfa2Excel -Tables $report -LiteralPath $ExcelFile } } function New-Pfa2VolumeShadowCopy() { <# .SYNOPSIS Creates a new volume shadow copy using the Diskshadow command. .DESCRIPTION This cmdlet will create a new volume shadow copy using the Diskshadow command, passing the variables specified. .PARAMETER Volume Required. A volume to add to the set. .PARAMETER Alias Required. Name of the shadow copy alias. .PARAMETER VerboseMode Optional. 'On' or 'Off'. If set to 'Off', verbose mode for the Diskshadow command is disabled. Default is 'On'. .INPUTS A volume to add to the set. .OUTPUTS diskshadow.exe output. .EXAMPLE New-Pfa2VolumeShadowCopy -Volume G: -Alias Prod Creates a new volume shadow copy of volume G: and assigns an alias named Prod. .EXAMPLE New-Pfa2VolumeShadowCopy -Volume G:, H: -Alias Prod Creates a new volume shadow copy of volumes G: and H: and assigns an aliases named Prod and Prod2 respectively. .EXAMPLE 'G:', 'H:' | New-Pfa2VolumeShadowCopy -Alias Prod Creates a new volume shadow copy of volumes G: and H: and assigns an aliases named Prod and Prod2 respectively. .NOTES See https://docs.microsoft.com/windows-server/administration/windows-commands/diskshadow for more information on the Diskshadow utility. #> [CmdletBinding()] Param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullorEmpty()] [string[]]$Volume, [Parameter(Mandatory)] [ValidateNotNullorEmpty()] [string]$Alias, [ValidateSet('On', 'Off')] [string]$VerboseMode = 'On' ) begin { $i = 1 $volumes = @() } process { $volumes += $Volume | ForEach-Object { "ADD VOLUME $_ ALIAS $Alias$(if ($i -gt 1) {$i}) PROVIDER {781c006a-5829-4a25-81e3-d5e43bd005ab}" $i++ } } end { $dsh = "./PUREVSS-SNAP.PFA" try { 'RESET', "SET VERBOSE $VerboseMode", 'SET CONTEXT PERSISTENT', 'SET OPTION TRANSPORTABLE', 'BEGIN BACKUP', $volumes, 'CREATE', 'END BACKUP', 'EXIT' | Set-Content $dsh DISKSHADOW /s $dsh } finally { Remove-Item $dsh -ErrorAction SilentlyContinue } } } function Mount-Pfa2HostVolumes() { <# .SYNOPSIS Sets Pure FlashArray connected disks to online. .DESCRIPTION This cmdlet will set any FlashArray volumes (disks) to online. .PARAMETER CimSession Optional. A CimSession or computer name. .INPUTS CimSession is optional. .OUTPUTS None .EXAMPLE Mount-Pfa2HostVolumes Set Pure FlashArray connected disks to online. .EXAMPLE Mount-Pfa2HostVolumes -CimSession 'myComputer' Set to online all Pure FlashArray connected to 'myComputer' with current credentials. .EXAMPLE $session = New-CimSession 'myComputer' -Credential (Get-Credential) Mount-Pfa2HostVolumes -CimSession $session Get-Pfa2HostBusAdapter -CimSession $session Set to online all Pure FlashArray connected to 'myComputer' and gets host bus adapter with previously created CIM session. .EXAMPLE Mount-Pfa2HostVolumes -CimSession (New-CimSession 'myComputer' -Credential $Creds) Set to online all Pure FlashArray connected to 'myComputer' with credentials stored in variable $Creds. .EXAMPLE Mount-Pfa2HostVolumes -CimSession (New-CimSession 'myComputer' -Credential (Get-Secret admin)) Set to online all Pure FlashArray connected to 'myComputer' with credentials stored in a secret vault. .EXAMPLE Mount-Pfa2HostVolumes -CimSession (New-CimSession 'myComputer' -Credential (Get-Credential)) Set to online all Pure FlashArray connected to 'myComputer'. Asks for credentials. .EXAMPLE 'myComputer' | Mount-Pfa2HostVolumes Set to online all Pure FlashArray connected to 'myComputer' with current credentials. .EXAMPLE $session = New-CimSession 'myComputer' -Credential (Get-Credential) $session | Mount-Pfa2HostVolumes Set to online all Pure FlashArray connected to 'myComputer' and gets host bus adapter with previously created CIM session. .EXAMPLE 'myComputer01', 'myComputer02' | Mount-Pfa2HostVolumes Set to online all Pure FlashArray connected to 'myComputer01' and 'myComputer02' with current credentials. .EXAMPLE $prod = [pscustomobject]@{Caption = 'Prod Server'; CimSession = 'myComputer'} $prod | Mount-Pfa2HostVolumes Set to online all Pure FlashArray connected to 'myComputer' with current credentials. #> [CmdletBinding(SupportsShouldProcess)] Param ( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNull()] [CimSession]$CimSession ) process { $params = @{} if ($PSBoundParameters.ContainsKey('CimSession')) { $params.Add('CimSession', $CimSession) } Update-HostStorageCache @params $disks = Get-Disk -FriendlyName 'PURE FlashArray*' @params | Where-Object {$null -ne $_.Number -and $_.OperationalStatus -ne 'Other'} foreach ($disk in $disks) { $label = if ($disk.PSComputerName) {" on $($disk.PSComputerName)"} if ($disk.IsReadOnly -and $PSCmdlet.ShouldProcess("Disk $($disk.Number)$label", 'Remove read-only attribute')) { $disk | Set-Disk -IsReadOnly $false @params } if ($disk.IsOffline -and $PSCmdlet.ShouldProcess("Disk $($disk.Number)$label", 'Set disk online')) { $disk | Set-Disk -IsOffline $false @params } } } } enum MPIODiskLBPolicy { clear = 0 # clear current policy and sets to Windows OS default of RR FO = 1 # Fail Over Only RR = 2 # Round Robin RRWS = 3 # Round Robin with Subset LQD = 4 # Least Queue Depth WP = 5 # Weighted Paths LB = 6 # Least Blocks } function Set-Pfa2MPIODiskLBPolicy() { <# .SYNOPSIS Sets the MPIO Load Balancing policy for FlashArray disks. .DESCRIPTION This cmdlet will set the MPIO Load Balancing policy for all connected Pure FlashArrays disks to the desired setting using the mpclaim.exe utlity. The default Windows OS setting is RR. .PARAMETER Policy Required. No default. The Policy type must be specified by the letter acronym for the policy name (ex. "RR" for Round Robin). Available options are: LQD = Least Queue Depth RR = Round Robin FO = Fail Over Only RRWS = Round Robin with Subset WP = Weighted Paths LB = Least Blocks clear = clears current policy and sets to Windows OS default of RR .INPUTS None .OUTPUTS mpclaim.exe output. .EXAMPLE Set-Pfa2MPIODiskLBPolicy -Policy LQD Sets the MPIO load balancing policy for all Pure disks to Least Queue Depth. .EXAMPLE Set-Pfa2MPIODiskLBPolicy -Policy clear Clears the current MPIO policy for all Pure disks and sets to the default of RR. #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [MPIODiskLBPolicy]$Policy ) #Checks whether mpclaim.exe is available. $exists = Test-Path "$env:systemroot\System32\mpclaim.exe" if (-not ($exists)) { Write-Error 'mpclaim.exe not found. Is MultiPathIO enabled? Exiting.' -ErrorAction Stop } Write-Host "Setting MPIO Load Balancing Policy to $([int]$Policy) for all Pure FlashArray disks." $drives = (Get-CimInstance -Namespace 'root\wmi' -Class 'mpio_disk_info').DriveInfo Get-PhysicalDisk -FriendlyName 'PURE FlashArray*' | ForEach-Object { $id = $drives | Where-Object SerialNumber -eq $_.UniqueId | ForEach-Object { $_.Name.Substring('MPIO Disk'.Length) } mpclaim.exe -l -d $id $([int]$Policy) } Write-Host 'New disk LB policy settings:' -ForegroundColor Green mpclaim.exe -s -d } function Backup-RegistryKey { [CmdletBinding(SupportsShouldProcess)] [OutputType([IO.FileInfo])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$KeyPath, [IO.FileInfo]$BackupFilePath, [switch]$Force, [Parameter(DontShow)] $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference'), [Parameter(DontShow)] $ConfirmPreference = $PSCmdlet.GetVariableValue('ConfirmPreference'), [Parameter(DontShow)] $WhatIfPreference = $PSCmdlet.GetVariableValue('WhatIfPreference') ) if ($Force -and ($ConfirmPreference -gt 'Low')) { $ConfirmPreference = 'None' } $catption = 'Registry key backup' $description = "Backup registry key $KeyPath" $warning = "Are you sure you want to back up registry key $KeyPath" if ($PSCmdlet.ShouldProcess($description, $warning, $catption)) { if (-not $Force -and $BackupFilePath -and (Test-Path $BackupFilePath)) { $query = "Are you sure you want to overwrite registry backup file $($BackupFilePath.FullName)" if (-not $PSCmdlet.ShouldContinue($query, $caption)) { Write-Error 'Cancelled by user' return } } elseif (-not $BackupFilePath) { $BackupFilePath = New-TemporaryFile } Write-Host "Creating registry backup in $($BackupFilePath.FullName)" reg export $KeyPath $BackupFilePath.FullName /Y if ($LASTEXITCODE) { Write-Error 'Registry backup failed' return } $BackupFilePath } } function Disable-Pfa2SecureChannelProtocol { <# .SYNOPSIS Disable a secure channel protocol. .DESCRIPTION This cmdlet will change Windows registry to disable specified secure channel protocol for client and server. .PARAMETER ProtocolName Required. Secure channel protocol name as <SSL/TLS/DTLS> <major version number>.<minor version number>. Run with -WhatIf common parameter to see the changes. .INPUTS A string containing protocol name via pipeline or parameter. .OUTPUTS None .EXAMPLE Disable-Pfa2SecureChannelProtocol 'TLS 1.1' Disables TLS 1.1. .LINK https://learn.microsoft.com/windows-server/security/tls/tls-registry-settings #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [Alias('Name')] [string]$ProtocolName, [Parameter(DontShow)] $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference'), [Parameter(DontShow)] $ConfirmPreference = $PSCmdlet.GetVariableValue('ConfirmPreference'), [Parameter(DontShow)] $WhatIfPreference = $PSCmdlet.GetVariableValue('WhatIfPreference') ) process { try { if (-not $PSCmdlet.ShouldProcess( $ProtocolName, "Disable secure channel protocol")) { return } Push-Location 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols' if (-not ( Test-Path $ProtocolName )) { New-Item -Path $ProtocolName | Out-Null } if (Test-Path $ProtocolName) { Write-Verbose "Disable Client $ProtocolName" $path = Join-Path $ProtocolName 'Client' if (-not (Test-Path $path)) { New-Item -Path $path | Out-Null } if (Test-Path $path) { Set-ItemProperty -Path $path -Value 0 -Type DWord -Name 'Enabled' Set-ItemProperty -Path $path -Value 1 -Type DWord -Name 'DisabledByDefault' } Write-Verbose "Disable Server $ProtocolName" $path = Join-Path $ProtocolName 'Server' if (-not (Test-Path $path)) { New-Item -Path $path | Out-Null } if (Test-Path $path) { Set-ItemProperty -Path $path -Value 0 -Type DWord -Name 'Enabled' Set-ItemProperty -Path $path -Value 1 -Type DWord -Name 'DisabledByDefault' } } } finally { Pop-Location } } } function Enable-Pfa2SecureChannelProtocol { <# .SYNOPSIS Enable a secure channel protocol. .DESCRIPTION This cmdlet will change Windows registry to enable specified secure channel protocol for client and server. .PARAMETER ProtocolName Required. Secure channel protocol name as <SSL/TLS/DTLS> <major version number>.<minor version number>. Run with -WhatIf common parameter to see the changes. .INPUTS A string containing protocol name via pipeline or parameter. .OUTPUTS None .EXAMPLE Enable-Pfa2SecureChannelProtocol 'TLS 1.2' Enables TLS 1.2. .LINK https://learn.microsoft.com/windows-server/security/tls/tls-registry-settings #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [Alias('Name')] [string]$ProtocolName, [Parameter(DontShow)] $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference'), [Parameter(DontShow)] $ConfirmPreference = $PSCmdlet.GetVariableValue('ConfirmPreference'), [Parameter(DontShow)] $WhatIfPreference = $PSCmdlet.GetVariableValue('WhatIfPreference') ) process { try { if (-not $PSCmdlet.ShouldProcess( $ProtocolName, "Enable secure channel protocol")) { return } Push-Location 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols' if (-not ( Test-Path $ProtocolName )) { New-Item -Path $ProtocolName | Out-Null } if (Test-Path $ProtocolName) { Write-Verbose "Enable Client $ProtocolName" $path = Join-Path $ProtocolName 'Client' if (-not (Test-Path $path)) { New-Item -Path $path | Out-Null } if (Test-Path $path) { Set-ItemProperty $path -Value 1 -Type DWord -Name 'Enabled' Set-ItemProperty $path -Value 0 -Type DWord -Name 'DisabledByDefault' } Write-Verbose "Enable Server $ProtocolName" $path = Join-Path $ProtocolName 'Server' if (-not (Test-Path $path)) { New-Item -Path $path | Out-Null } if (Test-Path $path) { Set-ItemProperty $path -Value 1 -Type DWord -Name 'Enabled' Set-ItemProperty $path -Value 0 -Type DWord -Name 'DisabledByDefault' } } } finally { Pop-Location } } } function Set-Pfa2TlsVersions { <# .SYNOPSIS Configures TLS secure channel protcols to follow best practice recommendations. .DESCRIPTION This cmdlet will alter Windows registry to disable outdated TLS secure channel protocols and enable recent versions. The minimum allowed version is specified as MinVersion parameter, which is '1.2' by default. This cmdlet makes a backup of the secure channel registry branc. It save the branch into a registry file. Use -WhatIf or -Verbose common parameters to see what protocols are anabled or disabled. Use -Confirm common parameter to control individual changes. .PARAMETER MinVersion Optional. '1.2' by default. Minimum allowed TLS version. .PARAMETER SkipBackup Optional. False by default. When present, Set-Pfa2TlsVersions does not make a registry backup. .PARAMETER BackupFilePath Optional. 'protocols.reg' by default. Sets path for registry backup. .PARAMETER Force Optional. False by default. When present suppresses all confirmations including registry backup file overwrite. .INPUTS Minimum allowed TLS version. .OUTPUTS None .EXAMPLE Set-TlsVersion Disable all TLS versinons below 1.2. Enable 1.2 and 1.3. Save registry branch backup as ./protocols.reg. .EXAMPLE Set-TlsVersion -SkipBackup Disable all TLS versinons below 1.2. Enable 1.2 and 1.3. Does not make registry backup. .EXAMPLE Set-TlsVersion -MinVersion 1.3 Disable all TLS versinons below 1.3. Enable 1.3. Save registry branch backup as ./protocols.reg. .EXAMPLE Set-TlsVersion -BackupFilePath 'D:\backup\tls.reg' Disable all TLS versinons below 1.2. Enable 1.2 and 1.3. Save registry branch backup as 'D:\backup\tls.reg'. .EXAMPLE Set-TlsVersion -Force Disable all TLS versinons below 1.2. Enable 1.2 and 1.3. Overwrite backup file ./protocols.reg if exists. .LINK https://learn.microsoft.com/windows-server/security/tls/tls-registry-settings #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(ValueFromPipeline)] [Version]$MinVersion = '1.2', [switch]$SkipBackup, [IO.FileInfo]$BackupFilePath = 'protocols.reg', [switch]$Force, [Parameter(DontShow)] $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference'), [Parameter(DontShow)] $ConfirmPreference = $PSCmdlet.GetVariableValue('ConfirmPreference'), [Parameter(DontShow)] $WhatIfPreference = $PSCmdlet.GetVariableValue('WhatIfPreference') ) if ($Force -and ($ConfirmPreference -gt 'Low')) { $ConfirmPreference = 'None' } $key = 'HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols' if (-not $SkipBackup) { Backup-RegistryKey -KeyPath $key -BackupFilePath $BackupFilePath -Force:$Force | Out-Null } 0..3 | ForEach-Object { $v = [Version]::new(1, $_) if ($v -lt $MinVersion) { Disable-Pfa2SecureChannelProtocol "TLS $v" } else { Enable-Pfa2SecureChannelProtocol "TLS $v" } } } function Set-Pfa2WindowsPowerScheme() { <# .SYNOPSIS Cmdlet to set the Power scheme for the Windows OS. .DESCRIPTION Cmdlet to set the Power scheme for the Windows OS to High Performance if no scheme id is specified. .PARAMETER PlanId Optional. A PlanId to activate on the system. .PARAMETER Session Optional. A PSSession to the remote computer. .INPUTS Session is optional. .OUTPUTS None .EXAMPLE Set-Pfa2WindowsPowerScheme Retrieves the current Power Scheme setting, and if not set to High Performance, sets it to active. .EXAMPLE $pssession = New-PSSession -ComputerName 'computer_name' -Credential (Get-Credential) Set-Pfa2WindowsPowerScheme -Session $pssession Retrieves the current Power Scheme setting on a remote computer, and if not set to High Performance, sets it to active. .EXAMPLE $pssession = New-PSSession -ComputerName 'computer_name' -Credential (Get-Credential) $pssession | Set-Pfa2WindowsPowerScheme Retrieves the current Power Scheme setting on a remote computer, and if not set to High Performance, sets it to active. .EXAMPLE $pssession = New-PSSession -ComputerName 'computer_name' -Credential (Get-Credential) $prod = [pscustomobject]@{Caption = 'Prod Server'; Session = $pssession} $prod | Set-Pfa2WindowsPowerScheme Retrieves the current Power Scheme setting on a remote computer, and if not set to High Performance, sets it to active. #> [CmdletBinding(SupportsShouldProcess)] Param ( [guid]$PlanId = "8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c", [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.Management.Automation.Runspaces.PSSession]$Session ) process { $params = @{} if ($PSBoundParameters.ContainsKey('Session')) { $params.Add('Session', $Session) } Invoke-Command { [CmdletBinding(SupportsShouldProcess)] Param ($p, $c, $w) $ConfirmPreference = $c $WhatIfPreference = $w $scheme = Get-CimInstance -Class 'Win32_PowerPlan' -Namespace 'root\cimv2\power' -Filter 'isActive=True' if ($scheme.InstanceID -ne "Microsoft:PowerPlan\{$p}") { if ($PSCmdlet.ShouldProcess("power scheme $p on $($env:COMPUTERNAME)", 'set active')) { powercfg.exe /setactive $p } } } -ArgumentList @($PlanId, $ConfirmPreference, $WhatIfPreference) @params } } function Test-Pfa2WindowsBestPractices() { <# .SYNOPSIS Cmdlet used to retrieve hosts information, test and optionally configure MPIO (FC) and/or iSCSI settings in a Windows OS against FlashArray Best Practices. .DESCRIPTION This cmdlet will retrieve the curretn host infromation, and iterate through several tests around MPIO (FC) and iSCSI OS settings and hardware, indicate whether they are adhearing to Pure Storage FlashArray Best Practices, and offer to alter the settings if applicable. All tests can be bypassed with a negative user response when prompted, or simply by using Ctrl-C to break the process. .PARAMETER Repair Optional. If this parameter is present, the cmdlet will repair settings to their recommended values. .PARAMETER IncludeIscsi Optional. If this parameter is present, the cmdlet will run tests for iSCSI settings. .PARAMETER LogFilePath Optional. Specify the full filepath (ex. c:\mylog.log) for logging. If not specified, the default file of %TMP%\BestPractices.log will be used. .INPUTS Optional parameter for iSCSI testing. .OUTPUTS Output status and best practice options for every test. .EXAMPLE Test-Pfa2WindowsBestPractices Run the cmdlet against the local machine running the MPIO tests and the log is located in the %TMP%\BestPractices.log file. .EXAMPLE Test-Pfa2WindowsBestPractices -IncludeIscsi -LogFilePath "c:\temp\mylog.log" Run the cmdlet against the local machine, run the additional iSCSI tests, and create the log file at c:\temp\mylog.log. .EXAMPLE Test-Pfa2WindowsBestPractices -Repair -IncludeIscsi -LogFilePath "c:\temp\mylog.log" Run the cmdlet against the local machine, run the additional iSCSI tests, repair settings to their recommended values, and create the log file at c:\temp\mylog.log. .EXAMPLE Test-Pfa2WindowsBestPractices -Repair -Confirm:$false Run the cmdlet against the local machine, repair settings to their recommended values skipping confirmation prompt. #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] Param ( [switch]$Repair, [string]$LogFilePath = (Join-Path $env:Temp 'BestPractices.log'), [switch]$IncludeIscsi, [switch]$Force ) $p = @{ 'logFilePath' = $LogFilePath; 'repair' = $Repair; 'force' = $Force; } $log = @{ 'path' = $LogFilePath; } 'Starting best practices verification' | Write-TestLog @log if (($PSVersionTable.PSVersion.Major -gt 5 -and -not $IsWindows) -or (Get-CimInstance -ClassName 'Win32_OperatingSystem').ProductType -lt 2) { 'Windows Server operating system is required.' | Write-TestLog -severity 'Failed' @log return } $ft = Get-WindowsFeature -Name 'Multipath-IO' if ($ft.InstallState -ne 'Installed') { if ($Force -or $PSCmdlet.ShouldContinue('Are you sure you want to install Multipath I/O feature', 'Multipath I/O feature')) { $res = Add-WindowsFeature -Name $ft.Name if (-not $res.Success) { 'Feature installation failed' | Write-TestLog -severity 'Failed' @log return } if ($res.RestartNeeded -eq 'Yes') { 'Server reboot required' | Write-TestLog -severity 'Warning' @log return } } else { 'Feature installation skipped' | Write-TestLog -severity 'Warning' @log return } } $inf = Get-SilComputer $inf | Write-TestLog @log $ms = Get-MPIOSetting $ms | Write-TestLog @log Test-Item -header 'MSDSM supported hardware' ` -valueDisplayName 'FlashArray device hardware id' ` -test { Get-MSDSMSupportedHW -VendorId 'PURE' -ProductId 'FlashArray' -ea SilentlyContinue } ` -action { New-MSDSMSupportedHW -VendorId 'PURE' -ProductId 'FlashArray' } @p Test-Item -header 'PathVerificationState' ` -valueDisplayName 'Enabled' ` -test { $ms.PathVerificationState -eq 'Enabled' } ` -action { Set-MPIOSetting -NewPathVerificationState 'Enabled' } @p $pdorp = if (-not (Test-AzureVm)) { 30 } else { 120 } #120 on Azure VM Test-Item -header 'PDORemovePeriod' ` -valueDisplayName "$pdorp" ` -test { $ms.PDORemovePeriod -eq $pdorp } ` -action { Set-MPIOSetting -NewPDORemovePeriod $pdorp } @p Test-Item -header 'UseCustomPathRecoveryTime' ` -valueDisplayName 'Enabled' ` -test { $ms.UseCustomPathRecoveryTime -eq 'Enabled' } ` -action { Set-MPIOSetting -CustomPathRecovery 'Enabled' } @p $pri = 20 Test-Item -header 'CustomPathRecoveryTime' ` -valueDisplayName "$pri" ` -test { $ms.CustomPathRecoveryTime -eq $pri } ` -action { Set-MPIOSetting -NewPathRecoveryInterval $pri } @p $dt = 60 Test-Item -header 'DiskTimeoutValue' ` -valueDisplayName "$dt" ` -test { $ms.DiskTimeoutValue -eq $dt } ` -action { Set-MPIOSetting -NewDiskTimeout $dt } @p $fsrg = 'HKLM:\System\CurrentControlSet\Control\FileSystem' Test-Item -header 'Delete notifications (trim or unmap)' ` -valueDisplayName "Enabled" ` -test { $ddn = Get-ItemProperty $fsrg 'DisableDeleteNotification' -ea SilentlyContinue ($null -eq $ddn) -or ($ddn.DisableDeleteNotification -eq 0) } ` -action { Set-ItemProperty $fsrg 'DisableDeleteNotification' 0 -Confirm:$false } @p if ($IncludeIscsi) { foreach ($adapter in Get-NetAdapter) { if ((-not $Repair) -or ($Force -or $PSCmdlet.ShouldContinue("Are you sure you want to repair $($adapter.Name) adapter", $adapter.Name))) { $adp = Get-NetAdapterAdvancedProperty -Name $adapter.Name -RegistryKeyword 'NetCfgInstanceId' -AllProperties $key = Join-Path 'HKLM:\System\CurrentControlSet\Services\Tcpip\Parameters\Interfaces' $adp.RegistryValue[0] Test-Item -header "$($adapter.Name) TcpAckFrequency" ` -valueDisplayName 1 ` -test { $taf = Get-ItemProperty $key 'TcpAckFrequency' -ea SilentlyContinue ($null -ne $taf) -and ($taf.TcpAckFrequency -eq 1) } ` -action { Set-ItemProperty $key 'TcpAckFrequency' 1 -Confirm:$false } @p Test-Item -header "$($adapter.Name) TcpNoDelay (Nagle)" ` -valueDisplayName 'Disabled (1)' ` -test { $tnd = Get-ItemProperty $key 'TcpNoDelay' -ea SilentlyContinue ($null -ne $tnd) -and ($tnd.TcpNoDelay -eq 1) } ` -action { Set-ItemProperty $key 'TcpNoDelay' 1 -Confirm:$false } @p } } } 'Best practices verification completed' | Write-TestLog @log } function Test-AzureVm() { $null -ne (Get-CimInstance -Query "SELECT Tag FROM Win32_SystemEnclosure WHERE SMBIOSAssetTag = '7783-7084-3265-9085-8269-3286-77'") } function Test-Item() { [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( [Parameter(ValueFromPipeline)] [scriptblock]$test, [string]$logFilePath, [string]$header = 'best practices', [string]$valueDisplayName = 'recommended value', [switch]$repair, [scriptblock]$action, [switch]$force ) $p = @{'path' = $logFilePath} Write-TestLog "Testing $header" @p if (Invoke-Command $test) { Write-TestLog "$header is $valueDisplayName" -Severity Passed @p } elseif ($repair -and ($force -or $PSCmdlet.ShouldProcess($header, "set to $valueDisplayName"))) { try { Write-TestLog "Repairing $header to $valueDisplayName" @p Invoke-Command $action | Out-Null Write-TestLog "$header is set to $valueDisplayName" -Severity Passed @p } catch { Write-TestLog "Failed setting $header to $valueDisplayName with error: $_" -Severity Failed @p } } else { Write-TestLog "$header does not have recommended value" -Severity Failed @p } } enum TestSeverity { Information = 0 Passed = 10 Warning = 14 Failed = 112 } function Write-TestLog { param( [Parameter(ValueFromPipeline)] [object]$inputObject, [string]$path, [TestSeverity]$severity = [TestSeverity]::Information ) $ev = if ($inputObject -is [string]) { $m = "$severity`: $inputObject" $p = if ($severity -gt 0) { @{ForegroundColor = [int]$severity % 100 } } Write-Host $m @p "$((Get-Date -f g).PadRight(20)) $m" } else { $inputObject } $ev | Out-File $path -Append -Confirm:$false -WhatIf:$false } function Write-Logo() { Write-Host '' Write-Host ' __________________________' Write-Host ' /++++++++++++++++++++++++++\' Write-Host ' /++++++++++++++++++++++++++++\' Write-Host ' /++++++++++++++++++++++++++++++\' Write-Host ' /++++++++++++++++++++++++++++++++\' Write-Host ' /++++++++++++++++++++++++++++++++++\' Write-Host ' /++++++++++++/----------\++++++++++++\' Write-Host ' /++++++++++++/ \++++++++++++\' Write-Host ' /++++++++++++/ \++++++++++++\' Write-Host ' /++++++++++++/ \++++++++++++\' Write-Host ' /++++++++++++/ \++++++++++++\' Write-Host ' \++++++++++++\ /++++++++++++/' Write-Host ' \++++++++++++\ /++++++++++++/' Write-Host ' \++++++++++++\ /++++++++++++/' Write-Host ' \++++++++++++\ /++++++++++++/' Write-Host ' \++++++++++++\ /++++++++++++/' Write-Host ' \++++++++++++\' Write-Host ' \++++++++++++\' Write-Host ' \++++++++++++\' Write-Host ' \++++++++++++\' Write-Host ' \------------\' Write-Host '' } function Dismount-Pfa2HostVolumes() { <# .SYNOPSIS Sets Pure FlashArray connected disks to offline. .DESCRIPTION This cmdlet will set any FlashArray volumes (disks) to offline. .PARAMETER CimSession Optional. A CimSession or computer name. .INPUTS CimSession is optional. .OUTPUTS None .EXAMPLE Dismount-Pfa2HostVolumes Set Pure FlashArray connected disks to offline. .EXAMPLE Dismount-Pfa2HostVolumes -CimSession 'myComputer' Set to offline all Pure FlashArray disks connected to 'myComputer' with current credentials. .EXAMPLE $session = New-CimSession 'myComputer' -Credential (Get-Credential) Dismount-Pfa2HostVolumes -CimSession $session Get-Pfa2HostBusAdapter -CimSession $session Set to offline all Pure FlashArray disks connected to 'myComputer' and gets host bus adapter with previously created CIM session. .EXAMPLE Dismount-Pfa2HostVolumes -CimSession (New-CimSession 'myComputer' -Credential $Creds) Set to offline all Pure FlashArray disks connected to 'myComputer' with credentials stored in variable $Creds. .EXAMPLE Dismount-Pfa2HostVolumes -CimSession (New-CimSession 'myComputer' -Credential (Get-Secret admin)) Set to offline all Pure FlashArray disks connected to 'myComputer' with credentials stored in a secret vault. .EXAMPLE Dismount-Pfa2HostVolumes -CimSession (New-CimSession 'myComputer' -Credential (Get-Credential)) Set to offline all Pure FlashArray disks connected to 'myComputer'. Asks for credentials. .EXAMPLE 'myComputer' | Dismount-Pfa2HostVolumes Set to offline all Pure FlashArray connected to 'myComputer' with current credentials. .EXAMPLE $session = New-CimSession 'myComputer' -Credential (Get-Credential) $session | Dismount-Pfa2HostVolumes Set to offline all Pure FlashArray connected to 'myComputer' and gets host bus adapter with previously created CIM session. .EXAMPLE 'myComputer01', 'myComputer02' | Dismount-Pfa2HostVolumes Set to offline all Pure FlashArray connected to 'myComputer01' and 'myComputer02' with current credentials. .EXAMPLE $prod = [pscustomobject]@{Caption = 'Prod Server'; CimSession = 'myComputer'} $prod | Dismount-Pfa2HostVolumes Set to offline all Pure FlashArray connected to 'myComputer' with current credentials. #> [CmdletBinding(SupportsShouldProcess)] Param ( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNull()] [CimSession]$CimSession ) process { $params = @{} if ($PSBoundParameters.ContainsKey('CimSession')) { $params.Add('CimSession', $CimSession) } Update-HostStorageCache @params $disks = Get-Disk -FriendlyName 'PURE FlashArray*' @params | Where-Object {$null -ne $_.Number -and $_.OperationalStatus -ne 'Other'} foreach ($disk in $disks) { $label = if ($disk.PSComputerName) {" on $($disk.PSComputerName)"} if (!$disk.IsOffline -and $PSCmdlet.ShouldProcess("Disk $($disk.Number)$label", 'Set disk offline')) { $disk | Set-Disk -IsOffline $true @params } } } } function Update-Pfa2DriveInformation() { <# .SYNOPSIS Updates drive letter and assigns a label. .DESCRIPTION Thsi cmdlet will update the current drive letter to the new drive letter, and assign a new file system label if specified. .PARAMETER DriveLetter Required. Specifies the drive letter of the partition to modify. .PARAMETER NewDriveLetter Required. Specifies the new drive letter for the partition. .PARAMETER NewFileSystemLabel Optional. Specifies a new file system label to use. .PARAMETER CimSession Optional. A CimSession or computer name. CIM session may be reused. .INPUTS CimSession is optional. .OUTPUTS None .EXAMPLE Update-Pfa2DriveInformation -DriveLetter M -NewDriveLetter S Updates the drive letter from M to S. .EXAMPLE Update-Pfa2DriveInformation -DriveLetter M -NewDriveLetter S -NewFileSystemLabel Test Updates the drive letter from M to S and changes the file system label to Test. .EXAMPLE Update-Pfa2DriveInformation -DriveLetter M -NewDriveLetter S -CimSession 'myComputer' Updates the drive letter from M to S. Update is performed on 'myComputer' with current credentials. .EXAMPLE $session = New-CimSession 'myComputer' -Credential (Get-Credential) Update-Pfa2DriveInformation -DriveLetter M -NewDriveLetter S -CimSession $session Updates the drive letter from M to S. Update is performed on 'myComputer' with previously created CIM session. .EXAMPLE Update-Pfa2DriveInformation -DriveLetter M -NewDriveLetter S -CimSession (New-CimSession 'myComputer' -Credential $Creds) Updates the drive letter from M to S. Update is performed on 'myComputer' with credentials stored in variable $Creds. .EXAMPLE Update-Pfa2DriveInformation -DriveLetter M -NewDriveLetter S -CimSession (New-CimSession 'myComputer' -Credential (Get-Secret admin)) Updates the drive letter from M to S. Update is performed on 'myComputer' with credentials stored in a secret vault. .EXAMPLE Update-Pfa2DriveInformation -DriveLetter M -NewDriveLetter S -CimSession (New-CimSession 'myComputer' -Credential (Get-Credential)) Updates the drive letter from M to S. Update is performed on 'myComputer'. Asks for credentials. .EXAMPLE $session = New-CimSession 'myComputer' -Credential (Get-Credential) $session | Update-Pfa2DriveInformation -DriveLetter M -NewDriveLetter S Updates the drive letter from M to S. Update is performed on 'myComputer' with previously created CIM session. .EXAMPLE $dev = [pscustomobject]@{Caption = 'Dev Server'; CimSession = 'myComputer'} $dev | Update-Pfa2DriveInformation -DriveLetter M -NewDriveLetter S Updates the drive letter from M to S. Update is performed on 'myComputer' with current credentials. #> [CmdletBinding(SupportsShouldProcess)] Param ( [Parameter(Mandatory)] [ValidateNotNullorEmpty()] [char]$DriveLetter, [Parameter(Mandatory)] [ValidateNotNullorEmpty()] [char]$NewDriveLetter, [string]$NewFileSystemLabel, [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNull()] [CimSession]$CimSession ) process { $params = @{ Query = "SELECT * FROM Win32_Volume WHERE DriveLetter = '$DriveLetter`:'" Property = @{ DriveLetter = "$NewDriveLetter`:" } } if ($PSBoundParameters.ContainsKey('NewFileSystemLabel')) { $params.Property.Add('Label', $NewFileSystemLabel) } if ($PSBoundParameters.ContainsKey('CimSession')) { $params.Add('CimSession', $CimSession) } Set-CimInstance @params | Out-Null } } # Declare exports Export-ModuleMember -Function Get-Pfa2HostBusAdapter Export-ModuleMember -Function Get-Pfa2SerialNumbers Export-ModuleMember -Function Get-Pfa2QuickFixEngineering Export-ModuleMember -Function Get-Pfa2VolumeShadowCopy Export-ModuleMember -Function Get-Pfa2WindowsDiagnosticInfo Export-ModuleMember -Function Get-Pfa2MPIODiskLBPolicy Export-ModuleMember -Function Set-Pfa2MPIODiskLBPolicy Export-ModuleMember -Function Set-Pfa2TlsVersions Export-ModuleMember -Function Set-Pfa2WindowsPowerScheme Export-ModuleMember -Function New-Pfa2VolumeShadowCopy Export-ModuleMember -Function Enable-Pfa2SecureChannelProtocol Export-ModuleMember -Function Disable-Pfa2SecureChannelProtocol Export-ModuleMember -Function Mount-Pfa2HostVolumes Export-ModuleMember -Function Dismount-Pfa2HostVolumes Export-ModuleMember -Function Update-Pfa2DriveInformation Export-ModuleMember -Function Test-Pfa2WindowsBestPractices Export-ModuleMember -Function New-Pfa2HypervClusterVolumeReport # END |