QualityCheck/QualityCheck.ps1 (3,496 lines of code) (raw):

<# .SYNOPSIS SAP on Azure Quality Check .DESCRIPTION The script will check the configuration of VMs running SAP software for Azure best practice .LINK https://github.com/Azure/SAP-on-Azure-Scripts-and-Utilities #> <# Copyright (c) Microsoft Corporation. Licensed under the MIT license. #> #Requires -Version 7.1 [CmdletBinding()] param ( # GUI [Parameter(Mandatory=$true, ParameterSetName='GUI')] [switch]$GUI, # Run multiple QC at once [Parameter(Mandatory=$true, ParameterSetName='MultiRun')] [switch]$MultiRun, # only run on VM guest OS [Parameter(Mandatory=$true, ParameterSetName='runlocally')] [switch]$RunLocally, [Parameter(Mandatory=$true, ParameterSetName='UserPassword')] [switch]$LogonWithUserPassword, [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKey')] [switch]$LogonWithUserPasswordSSHKey, [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKeyPassphrase')] [switch]$LogonWithUserPasswordSSHKeyPassphrase, [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvault')] [switch]$LogonWithUserPasswordAzureKeyvault, [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [switch]$LogonWithUserPasswordAzureKeyvaultSSHKey, [Parameter(Mandatory=$true, ParameterSetName='UserAsRootSSHKey')] [switch]$LogonAsRootSSHKey, [Parameter(Mandatory=$true, ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [switch]$LogonAsRootAzureKeyvaultSSHKey, [Parameter(Mandatory=$true, ParameterSetName='UserSSHKey')] [switch]$LogonWithUserSSHKey, # VM Operating System [Parameter(Mandatory=$true, ParameterSetName='runlocally')] [Parameter(Mandatory=$true, ParameterSetName='UserPassword')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKey')] [ValidateSet("Windows", "SUSE", "RedHat", "OracleLinux",IgnoreCase = $false)] [string]$VMOperatingSystem, # Database running SAP [Parameter(Mandatory=$true, ParameterSetName='runlocally')] [Parameter(Mandatory=$true, ParameterSetName='UserPassword')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserSSHKey')] [ValidateSet("HANA","Oracle","MSSQL","Db2","ASE",IgnoreCase = $false)] [string]$VMDatabase, # Which component to check [Parameter(Mandatory=$true, ParameterSetName='runlocally')] [Parameter(Mandatory=$true, ParameterSetName='UserPassword')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserSSHKey')] [ValidateSet("DB", "ASCS", "APP",IgnoreCase = $false)] [string]$VMRole, # VM Resource Group Name [Parameter(Mandatory=$true, ParameterSetName='UserPassword')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserSSHKey')] [string]$AzVMResourceGroup, # Azure VM Name [Parameter(Mandatory=$true, ParameterSetName='UserPassword')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserSSHKey')] [string]$AzVMName, # VM Hostname or IP address (used to connect) [Parameter(Mandatory=$true, ParameterSetName='UserPassword')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserSSHKey')] [string]$VMHostname, # VM Username [Parameter(Mandatory=$true, ParameterSetName='UserPassword')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserSSHKey')] [string]$VMUsername, # VM Password [Parameter(Mandatory=$true, ParameterSetName='UserPassword')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKeyPassphrase')] [System.Security.SecureString]$VMPassword, # VM Connection Port (Linux SSH Port) [Parameter(ParameterSetName='UserPassword')] [Parameter(ParameterSetName='UserPasswordSSHKey')] [Parameter(ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserAsRootSSHKey')] [Parameter(ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserSSHKey')] [string]$VMConnectionPort="22", # Run HA checks [Parameter(ParameterSetName='runlocally')] [Parameter(ParameterSetName='UserPassword')] [Parameter(ParameterSetName='UserPasswordSSHKey')] [Parameter(ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserAsRootSSHKey')] [Parameter(ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserSSHKey')] [boolean]$HighAvailability=$false, # ConfigFile that contains the checks to be executed [Parameter(ParameterSetName='runlocally')] [Parameter(ParameterSetName='UserPassword')] [Parameter(ParameterSetName='UserPasswordSSHKey')] [Parameter(ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserAsRootSSHKey')] [Parameter(ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserSSHKey')] [string]$ConfigFileName="QualityCheck.json", # SAP SID, for HANA DB SID [Parameter(ParameterSetName='runlocally')] [Parameter(ParameterSetName='UserPassword')] [Parameter(ParameterSetName='UserPasswordSSHKey')] [Parameter(ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserAsRootSSHKey')] [Parameter(ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserSSHKey')] [string]$SID, # HANA Data Directories [Parameter(ParameterSetName='runlocally')] [Parameter(ParameterSetName='UserPassword')] [Parameter(ParameterSetName='UserPasswordSSHKey')] [Parameter(ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserAsRootSSHKey')] [Parameter(ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserSSHKey')] [string[]]$DBDataDir="/hana/data", # HANA Log Directories [Parameter(ParameterSetName='runlocally')] [Parameter(ParameterSetName='UserPassword')] [Parameter(ParameterSetName='UserPasswordSSHKey')] [Parameter(ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserAsRootSSHKey')] [Parameter(ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserSSHKey')] [string[]]$DBLogDir="/hana/log", # HANA Shared Directory [Parameter(ParameterSetName='runlocally')] [Parameter(ParameterSetName='UserPassword')] [Parameter(ParameterSetName='UserPasswordSSHKey')] [Parameter(ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserAsRootSSHKey')] [Parameter(ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserSSHKey')] [string]$DBSharedDir="/hana/shared", # SSH Keys [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserSSHKey')] [string]$SSHKey, # SSH Key Passphrase [Parameter(Mandatory=$true, ParameterSetName='UserPasswordSSHKeyPassphrase')] [System.Security.SecureString]$SSHKeyPassphrase, # Keyvault Resource Group [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [string]$KeyVaultResourceGroup, # Keyvault Name [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [string]$KeyVaultName, # Keyvault Entry [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(Mandatory=$true, ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(Mandatory=$true, ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [string]$KeyVaultEntry, # ANF Resource Group [Parameter(ParameterSetName='UserPassword')] [Parameter(ParameterSetName='UserPasswordSSHKey')] [Parameter(ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserAsRootSSHKey')] [Parameter(ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserSSHKey')] [string]$ANFResourceGroup, # ANF Account Name [Parameter(ParameterSetName='UserPassword')] [Parameter(ParameterSetName='UserPasswordSSHKey')] [Parameter(ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserAsRootSSHKey')] [Parameter(ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserSSHKey')] [string]$ANFAccountName, # Hardwaretype (VM or HLI) [Parameter(ParameterSetName='UserPassword')] [Parameter(ParameterSetName='UserPasswordSSHKey')] [Parameter(ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserAsRootSSHKey')] [Parameter(ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserSSHKey')] [string]$Hardwaretype="VM", # HANA Deployment Model [Parameter(ParameterSetName='runlocally')] [Parameter(ParameterSetName='UserPassword')] [Parameter(ParameterSetName='UserPasswordSSHKey')] [Parameter(ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserAsRootSSHKey')] [Parameter(ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserSSHKey')] [string][ValidateSet("OLTP","OLAP","OLTP-ScaleOut","OLAP-ScaleOut",IgnoreCase = $false)]$HANADeployment="OLTP", # High Availability Agent [Parameter(ParameterSetName='runlocally')] [Parameter(ParameterSetName='UserPassword')] [Parameter(ParameterSetName='UserPasswordSSHKey')] [Parameter(ParameterSetName='UserPasswordSSHKeyPassphrase')] [Parameter(ParameterSetName='UserPasswordAzureKeyvault')] [Parameter(ParameterSetName='UserPasswordAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserAsRootSSHKey')] [Parameter(ParameterSetName='UserAsRootAzureKeyvaultSSHKey')] [Parameter(ParameterSetName='UserSSHKey')] [string][ValidateSet("SBD","FencingAgent","WCF",IgnoreCase = $false)]$HighAvailabilityAgent="SBD", # Run multiple QC at once [Parameter(Mandatory=$true, ParameterSetName='MultiRun')] [string]$ImportFile, # add JSON output in addition to HTML file [switch]$AddJSONFile, # add Output Directory for HTML and JSON [string]$OutputDirName, # add detailed logs [switch]$DetailedLog, # Subscription ID [string]$SubscriptionId, # Write to specific debug file [switch]$DetailedDebugFile ) # defining script version $scriptversion = 2025040601 function LoadHTMLHeader { $script:_HTMLHeader = @" <style> h1 { font-family: Arial, Helvetica, sans-serif; color: #e68a00; font-size: 28px; } h2 { font-family: Arial, Helvetica, sans-serif; color: #000099; font-size: 16px; } body { font-family: Arial, Helvetica, sans-serif; } table { font-size: 12px; border: 0px; font-family: Lucida Console, monospace; } td { padding: 4px; margin: 0px; border: 0; white-space: pre; vertical-align: top; } th { background: #395870; background: linear-gradient(#49708f, #293f50); color: #fff; font-size: 11px; text-transform: uppercase; padding: 10px 15px; vertical-align: middle; vertical-align: top; } tbody tr:nth-child(even) { background: #f0f0f2; } #CreationDate { font-family: Arial, Helvetica, sans-serif; color: #ff3300; font-size: 12px; } #Code { font-family: Courier New, monospace; font-size: 12px; } .StatusError { color: #ff0000; } .StatusWarning { color: #ffa500; } .StatusOK { color: #008000; } .StatusInfo { color: #0026ff; } </style> "@ $script:_Content = "<h1>SAP on Azure Quality Check</h1><h2>Execution date: $(Get-Date)</h2><br><h2>Use the links to jump to the sections:</h2>" } # RunLog function for more detailed data during execution function WriteRunLog { [CmdletBinding()] param ( [string]$message, [ValidateSet("INFO","WARNING","ERROR")][string]$category="INFO" ) switch ($category) { "INFO" { $_prestring = "INFO - " $_color = "Green" } "WARNING" { $_prestring = "WARNING - " $_color = "Yellow" } "ERROR" { $_prestring = "ERROR - " $_color = "Red" } } $_runlog_row = "" | Select-Object "Log" $_runlog_row.Log = [string]$_prestring + [string]$message $script:_runlog += $_runlog_row if (-not $RunLocally) { Write-Host ($_prestring + $message) -ForegroundColor $_color } } function WriteDetailedDebugLog([String]$logmessage) { if ($script:DetailedDebugFile) { Add-Content -Path $script:_WriteDetailedDebugFileName $logmessage } } # CheckRequiredModules - checking for installed Modules and their versions function CheckRequiredModules { # checking PowerShell version if (($PSVersionTable.PSVersion.Major -ge 7) -and ($PSVersionTable.PSVersion.Minor -ge 1)) { # PowerSehll 7.1 installed } else { # PowerShell 7.1 or higher required Write-Error "Please install PowerShell 7.1 or newer" exit } # looping through modules in json file foreach ($_requiredmodule in $_jsonconfig.PowerShellPrerequisits) { # check if module is available $_modules = Get-Module -ListAvailable -Name $_requiredmodule.ModuleName if ($_modules) { # module installed, checking for version WriteRunLog -message ("Module " + $_requiredmodule.ModuleName + " installed") $_foundmoduleversion = 0 foreach ($_module in $_modules) { $_requiredmoduleversion = [version]$_requiredmodule.Version if ($_module.Version -ge $_requiredmoduleversion) { # found a module version equal or greater then required version $_foundmoduleversion = 1 break } } # check if loop found the required version if ($_foundmoduleversion -eq 0) { # required module version not found WriteRunLog -category "ERROR" -message ("Please install " + $_requiredmodule.ModuleName + " with version greater than " + $_requiredmodule.Version) exit } } else { # Get-Module didn't come back with a result WriteRunLog -category "ERROR" -message ("Please install " + $_requiredmodule.ModuleName + " with version greater than " + $_requiredmodule.Version) exit } } } # function creates an object out of sgmap string # each text column is return as a part of the object function ConvertFrom-String_sgmap { [CmdletBinding()] param ( [object]$p ) # create empty array $_output = @() # replace characters between numbers with a ',' $_x = $p.Trim() -replace '\s+',',' # create a table object $_x | Foreach-Object { $_output += $_ | ConvertFrom-Csv -Header P1,P2,P3,P4,P5,P6,P7,P8,P9,P10,P11 } # return object return $_output } function ConvertFrom-String_lsscsi { [CmdletBinding()] param ( [object]$p ) # create empty array $_output = @() # replace characters between numbers with a ',' $_x = $p.Trim() -replace '\s+',',' # create a table object $_x | Foreach-Object { $_output += $_ | ConvertFrom-Csv -Header P1,P2,P3,P4,P5,P6,P7,P8,P9,P10 } # return object return $_output } # convert a df text output to object function ConvertFrom-String_df { [CmdletBinding()] param ( [object]$p ) # create empty array $_output = @() # replace all characters with ',' $_x = $p.Trim() -replace '\s+',',' # remove first line as it is the header if ($_x[0].Contains("Filesystem")) { $_x = $_x[1..($_x.Length-1)] } # create a table object $_x | Foreach-Object { $_output += $_ | ConvertFrom-Csv -Header Filesystem,Size,Used,Free,UsedPercent,Mountpoint } # return object return $_output } # convert findmnt output to filesystem object function ConvertFrom-String_findmnt { [CmdletBinding()] param ( [object]$p ) # create empty object $_output = @() # replace all characters with ',' # $_x = $p.Trim() -replace '\s+',',' $_x = $p.Trim() -replace '\s+',';' # create table object $_x | Foreach-Object { # $_output += $_ | ConvertFrom-Csv -Header target,source,fstype,options $_output += $_ | ConvertFrom-Csv -Header target,source,fstype,options -Delimiter ";" } # return object return $_output } # check if TCP connectivity is available (firewall rules allow access to system) function CheckTCPConnectivity { if ($VMOperatingSystem -eq "Windows") { } else { try { # create a TCP connection to VM using specified port $_testresult = New-Object System.Net.Sockets.TcpClient($VMHostname, $VMConnectionPort) if ($_testresult.Connected) { # connected $script:_CheckTCPConnectivityResult = $true $_minimumlatency = 120 $_ping = Test-Connection -Ping -IPv4 -TargetName $VMHostname -Count 1 if ($_ping.Status -eq "Success") { if ($_ping.Latency -gt $_minimumlatency) { $script:_sshstreamwait = $_ping.Latency WriteRunLog -category "INFO" -message "Ping successful - setting SSH Stream Latency to $script:_sshstreamwait ms" } else { $script:_sshstreamwait = $_minimumlatency WriteRunLog -category "INFO" -message "Ping successful - setting SSH Stream Latency to minimum of $script:_sshstreamwait ms" } } else { $script:_sshstreamwait = 700 WriteRunLog -category "WARNING" -message "Ping might be blocked - setting SSH Stream Latency to $script:_sshstreamwait ms" } } } catch { WriteRunLog -category "ERROR" -message "Error connecting to $AzVMName using $VMHostname, please check network connection and firewall rules" $script:_CheckTCPConnectivityResult = $false exit } } } # create a connection to the system function ConnectVM { if ($VMOperatingSystem -eq "Windows") { # Connect to Windows # New-PSSession in the future } else { # removing trusted host to make sure there is no error in case the host ssh keys were changed if ($DetailedLog) { WriteRunLog -category "INFO" -message ("ConnectVM - Removing SSH Trusted Hosts") } Get-SSHTrustedHost -HostName $VMHostname | Remove-SSHTrustedHost -ErrorAction SilentlyContinue | Out-Null if ($script:LogonWithUserPassword -or (($script:GUILogonMethod -eq "UserPassword") -and $GUI ) -or ($Script:MultiRun) ) { # create a pasword hash that will be used to connect when using sudo commands $script:_ClearTextPassword = ConvertFrom-SecureString -SecureString $VMPassword -AsPlainText # create credentials object $script:_credentials = New-Object System.Management.Automation.PSCredential ($VMUsername, $VMPassword); # connect to VM if ($DetailedLog) { WriteRunLog -category "INFO" -message ("ConnectVM - Connecting with New-SSHSession") } $script:_sshsession = New-SSHSession -ComputerName $VMHostname -Credential $_credentials -Port $VMConnectionPort -AcceptKey -ConnectionTimeout 5 -ErrorAction SilentlyContinue } switch ($PsCmdlet.ParameterSetName) { "UserPassword" { if ($DetailedLog) { WriteRunLog -category "INFO" -message ("ConnectVM - Section UserPassword") } } "UserPasswordSSHKey" { if ($DetailedLog) { WriteRunLog -category "INFO" -message ("ConnectVM - Section UserPasswordSSHKey") } # create a pasword hash that will be used to connect when using sudo commands $script:_ClearTextPassword = ConvertFrom-SecureString -SecureString $VMPassword -AsPlainText # create credentials object $_nopasswd = New-Object System.Security.SecureString $script:_credentials = New-Object System.Management.Automation.PSCredential ($VMUsername, $_nopasswd); if (-not(Test-Path -Path $SSHKey -PathType Leaf)) { WriteRunLog -category "ERROR" -message "Can't find SSH Key file, please check path" $script:_ConnectVMResult = $false } # connect to VM $script:_sshsession = New-SSHSession -ComputerName $VMHostname -Credential $_credentials -Port $VMConnectionPort -KeyFile $SSHKey -AcceptKey -ConnectionTimeout 5 -ErrorAction SilentlyContinue } "UserSSHKey" { if ($DetailedLog) { WriteRunLog -category "INFO" -message ("ConnectVM - Section UserSSHKey") } # create credentials object $_nopasswd = New-Object System.Security.SecureString $script:_credentials = New-Object System.Management.Automation.PSCredential ($VMUsername, $_nopasswd); if (-not(Test-Path -Path $SSHKey -PathType Leaf)) { WriteRunLog -category "ERROR" -message "Can't find SSH Key file, please check path" $script:_ConnectVMResult = $false } # connect to VM $script:_sshsession = New-SSHSession -ComputerName $VMHostname -Credential $_credentials -Port $VMConnectionPort -KeyFile $SSHKey -AcceptKey -ConnectionTimeout 5 -ErrorAction SilentlyContinue } "UserPasswordSSHKeyPassphrase" { if ($DetailedLog) { WriteRunLog -category "INFO" -message ("ConnectVM - Section UserPasswordSSHKeyPassphrase") } # create a pasword hash that will be used to connect when using sudo commands $script:_ClearTextPassword = ConvertFrom-SecureString -SecureString $VMPassword -AsPlainText # create credentials object $script:_credentials = New-Object System.Management.Automation.PSCredential ($VMUsername, $SSHKeyPassphrase); try { # connect to VM $script:_sshsession = New-SSHSession -ComputerName $VMHostname -Credential $_credentials -Port $VMConnectionPort -KeyFile $SSHKey -AcceptKey -ConnectionTimeout 5 -ErrorAction SilentlyContinue } catch { WriteRunLog -category "ERROR" -message "Authentication failed, please check your credentials and keys." WriteRunLog -category "ERROR" -message "Only old keys are supported by SSH.NET library using passphrases." WriteRunLog -category "ERROR" -message "Use this command to generate a supported key: ssh-keygen -m PEM -t rsa -b 4096" $script:_ConnectVMResult = $false } } "UserPasswordAzureKeyvault" { if ($DetailedLog) { WriteRunLog -category "INFO" -message ("ConnectVM - Section UserPasswordAzureKeyvault") } # create a pasword hash that will be used to connect when using sudo commands $script:_ClearTextPassword = Get-AzKeyVaultSecret -VaultName $KeyVaultName -Name $KeyVaultEntry -AsPlainText $VMPassword = ConvertTo-SecureString -String $script:_ClearTextPassword -AsPlainText -Force # create credentials object $script:_credentials = New-Object System.Management.Automation.PSCredential ($VMUsername, $VMPassword); # connect to VM $script:_sshsession = New-SSHSession -ComputerName $VMHostname -Credential $_credentials -Port $VMConnectionPort -AcceptKey -ConnectionTimeout 5 -ErrorAction SilentlyContinue } "UserPasswordAzureKeyvaultSSHKey" { if ($DetailedLog) { WriteRunLog -category "INFO" -message ("ConnectVM - Section UserPasswordAzureKeyvaultSSHKey") } # create a pasword hash that will be used to connect when using sudo commands $script:_ClearTextPassword = ConvertFrom-SecureString -SecureString $VMPassword -AsPlainText $_keystring = Get-AzKeyVaultKey -VaultName $KeyVaultName -Name $KeyVaultEntry -AsPlainText # create credentials object $_nopasswd = New-Object System.Security.SecureString $script:_credentials = New-Object System.Management.Automation.PSCredential ($VMUsername, $_nopasswd); # connect to VM $script:_sshsession = New-SSHSession -ComputerName $VMHostname -Credential $_credentials -Port $VMConnectionPort -KeyString $_keystring -AcceptKey -ConnectionTimeout 5 -ErrorAction SilentlyContinue } "UserAsRootSSHKey" { if ($DetailedLog) { WriteRunLog -category "INFO" -message ("ConnectVM - Section UserAsRootSSHKey") } # create a pasword hash that will be used to connect when using sudo commands $script:_ClearTextPassword = "" # create credentials object $_nopasswd = New-Object System.Security.SecureString $script:_credentials = New-Object System.Management.Automation.PSCredential ($VMUsername, $_nopasswd); if (-not(Test-Path -Path $SSHKey -PathType Leaf)) { WriteRunLog -category "ERROR" -message "Can't find SSH Key file, please check path" $script:_ConnectVMResult = $false } # connect to VM $script:_sshsession = New-SSHSession -ComputerName $VMHostname -Credential $_credentials -Port $VMConnectionPort -KeyFile $SSHKey -AcceptKey -ConnectionTimeout 5 -ErrorAction SilentlyContinue } "UserAsRootAzureKeyvaultSSHKey" { if ($DetailedLog) { WriteRunLog -category "INFO" -message ("ConnectVM - Section UserAsRootAzureKeyvaultSSHKey") } $_keystring = Get-AzKeyVaultKey -VaultName $KeyVaultName -Name $KeyVaultEntry -AsPlainText # create credentials object $_nopasswd = New-Object System.Security.SecureString $script:_credentials = New-Object System.Management.Automation.PSCredential ($VMUsername, $_nopasswd); # connect to VM $script:_sshsession = New-SSHSession -ComputerName $VMHostname -Credential $_credentials -Port $VMConnectionPort -KeyString $_keystring -AcceptKey -ConnectionTimeout 5 -ErrorAction SilentlyContinue } } # connecting to linux with SSH keys # $script:_sshsession = New-SSHSession -ComputerName $VMHostname -Credential $_credentials -Port $VMConnectionPort -KeyFile $SSHKey -AcceptKey -ConnectionTimeout 5 -ErrorAction SilentlyContinue # check if connection is successful (user/password/sshkeys correct) if ($script:_sshsession.Connected -eq $true) { if ($DetailedLog) { WriteRunLog -category "INFO" -message ("ConnectVM - Connected using SSH, creating SSH Stream") } # return SSH session ID for later use $script:_ConnectVMResult = $true # add an SSH Stream $script:_sshstream = New-SSHShellStream -SSHSession $_sshsession -BufferSize 200000 -Columns 200 -Rows 1000 -Height 1500 return $script:_sshsession.SessionId } else { # not able to connect WriteRunLog -category "ERROR" -message "SSH - Please check your credentials, unable to logon" $script:_ConnectVMResult = $false exit } } } # collect script parameters and put them into a table object function CollectScriptParameters { # create empty array $_outputarray = @() # each section is a separate line in the report # Operating System $_outputarray_row = "" | Select-Object Parameter,Value $_outputarray_row.Parameter = "Operating System" $_outputarray_row.Value = $VMOperatingSystem $_outputarray += $_outputarray_row # Database Type $_outputarray_row = "" | Select-Object Parameter,Value $_outputarray_row.Parameter = "Database" $_outputarray_row.Value = $VMDatabase $_outputarray += $_outputarray_row # VM Role (DB/ASCS/APP) $_outputarray_row = "" | Select-Object Parameter,Value $_outputarray_row.Parameter = "VM Role" $_outputarray_row.Value = $VMRole $_outputarray += $_outputarray_row # Azure Resource Group Name $_outputarray_row = "" | Select-Object Parameter,Value $_outputarray_row.Parameter = "Azure Resource Group" $_outputarray_row.Value = $AzVMResourceGroup $_outputarray += $_outputarray_row # Azure VM Name $_outputarray_row = "" | Select-Object Parameter,Value $_outputarray_row.Parameter = "Azure VM Name" $_outputarray_row.Value = $AzVMName $_outputarray += $_outputarray_row # Hostname or IP Address $_outputarray_row = "" | Select-Object Parameter,Value $_outputarray_row.Parameter = "VM Hostname / IP Address" $_outputarray_row.Value = $VMHostname $_outputarray += $_outputarray_row # VM Username used to log on $_outputarray_row = "" | Select-Object Parameter,Value $_outputarray_row.Parameter = "VM Username" $_outputarray_row.Value = $VMUsername $_outputarray += $_outputarray_row # Hardware Type (VM/HLI) $_outputarray_row = "" | Select-Object Parameter,Value $_outputarray_row.Parameter = "Hardware Type" $_outputarray_row.Value = $Hardwaretype $_outputarray += $_outputarray_row # High Availability Yes/No $_outputarray_row = "" | Select-Object Parameter,Value $_outputarray_row.Parameter = "High Availability Check" $_outputarray_row.Value = $HighAvailability $_outputarray += $_outputarray_row # if HANA, then HANA Scenario if ($VMDatabase -eq "HANA") { $_outputarray_row = "" | Select-Object Parameter,Value $_outputarray_row.Parameter = "SAP HANA Scenario" $_outputarray_row.Value = $HANADeployment $_outputarray += $_outputarray_row } # if High Availbility, then HA Fencing mechanism if ($HighAvailability -eq $true) { $_outputarray_row = "" | Select-Object Parameter,Value $_outputarray_row.Parameter = "Fencing Mechansim" $_outputarray_row.Value = $HighAvailabilityAgent $_outputarray += $_outputarray_row } # convert the output to HTML $_outputarray = $_outputarray | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""ScriptParameter"">Script Parameters</h2>Here are the parameters handed over to the script" $_outputarray = $_outputarray.Replace("::","<br/>") # add link to index of HTML file $script:_Content += "<a href=""#ScriptParameter"">Script Parameter</a><br>" $_outputarray } function CollectPowerShellDetails { # create empty array $_outputarray = @() $_outputarray_row = "" | Select-Object Description, Value $_outputarray_row.Description = "PowerShell Version" $_outputarray_row.Value = [string]$PSVersionTable.PSVersion $_outputarray += $_outputarray_row $_outputarray_row = "" | Select-Object Description, Value $_outputarray_row.Description = "PowerShell Operating System" $_outputarray_row.Value = [string]$PSVersionTable.OS $_outputarray += $_outputarray_row $_outputarray_row = "" | Select-Object Description, Value $_outputarray_row.Description = "PowerShell Edition" $_outputarray_row.Value = [string]$PSVersionTable.PSEdition $_outputarray += $_outputarray_row $_outputarray_row = "" | Select-Object Description, Value $_outputarray_row.Description = "PowerShell Module Posh-SSH" $_outputarray_row.Value = [string](Get-InstalledModule Posh-SSH).Version $_outputarray += $_outputarray_row $_outputarray_row = "" | Select-Object Description, Value $_outputarray_row.Description = "PowerShell Module Az" $_outputarray_row.Value = [string](Get-InstalledModule Az).Version $_outputarray += $_outputarray_row # convert the output to HTML $_outputarray = $_outputarray | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""PowerShellDetails"">PowerShell Details</h2>Here are PowerShell Details" $_outputarray = $_outputarray.Replace("::","<br/>") # add link to index of HTML file $script:_Content += "<a href=""#PowerShellDetails"">PowerShell Details</a><br>" $_outputarray } function CollectVMInformation { # create empty array $_outputarray = @() if ($VMOperatingSystem -eq "Windows") { # windows code } else { # Linux Code $_command = PrepareCommand -Command "curl -s -H Metadata:true --noproxy '*' 'http://169.254.169.254/metadata/instance?api-version=2021-02-01';echo" -CommandType "OS" $_result = RunCommand -p $_command $script:_vmmetadata = $_result | ConvertFrom-Json # check OS for NVMe devices $_command = PrepareCommand -Command "ls -l /dev/nvme* 2>/dev/null | wc -l" -CommandType "OS" $_result = RunCommand -p $_command if ($_result -gt 0) { WriteRunLog -category "INFO" -message "Detected NVMe devices, setting controller type" $script:_DiskControllerType = "NVMe" } else { WriteRunLog -category "INFO" -message "Detected SCSI devices, setting controller type" $script:_DiskControllerType = "SCSI" } } # VM Type $_outputarray_row = "" | Select-Object CheckID, Description, Output $_outputarray_row.CheckID = "IC-VMType" $_outputarray_row.Description = "VM SKU" $_outputarray_row.Output = $script:_vmmetadata.compute.vmSize $script:_VMType = $script:_vmmetadata.compute.vmSize $_outputarray += $_outputarray_row # VM Id $_outputarray_row = "" | Select-Object CheckID, Description, Output $_outputarray_row.CheckID = "IC-vmId" $_outputarray_row.Description = "VM Unique ID" $_outputarray_row.Output = $script:_vmmetadata.compute.vmId $_outputarray += $_outputarray_row # VM location $_outputarray_row = "" | Select-Object CheckID, Description, Output $_outputarray_row.CheckID = "IC-vmlocation" $_outputarray_row.Description = "Azure Region" $_outputarray_row.Output = $script:_vmmetadata.compute.location $_outputarray += $_outputarray_row # VM name $_outputarray_row = "" | Select-Object CheckID, Description, Output $_outputarray_row.CheckID = "IC-vmname" $_outputarray_row.Description = "VM Name" $_outputarray_row.Output = $script:_vmmetadata.compute.name $_outputarray += $_outputarray_row # VM zone $_outputarray_row = "" | Select-Object CheckID, Description, Output $_outputarray_row.CheckID = "IC-vmzone" $_outputarray_row.Description = "Availability Zone" if ([string]::IsNullOrEmpty($script:_vmmetadata.compute.zone)) { $_outputarray_row.Output = "No Availability Zone" } else { $_outputarray_row.Output = $script:_vmmetadata.compute.zone } $_outputarray += $_outputarray_row # Disk Controller Type $_outputarray_row = "" | Select-Object CheckID, Description, Output $_outputarray_row.CheckID = "IC-Controller" $_outputarray_row.Description = "Disk Controller" $_outputarray_row.Output = $script:_DiskControllerType $_outputarray += $_outputarray_row # looping through VM checks foreach ($_CollectVMInformationCheck in $_jsonconfig.VMCollectInformation) { # check if CollechtVMInformation tasks needs to be executed against this OS and database if ( $_CollectVMInformationCheck.OS.Contains($VMOperatingSystem) -and ` $_CollectVMInformationCheck.DB.Contains($VMDatabase) -and ` $_CollectVMInformationCheck.Role.Contains($VMRole) -and ` ($_CollectVMInformationCheck.OSVersion.Contains("all") -or $_CollectVMInformationCheck.OSVersion.Contains($VMOSRelease)) -and ` $_CollectVMInformationCheck.Hardwaretype.Contains($Hardwaretype)) { # check if check applies to HA or not and if HA check for HA-Agent if (($_CollectVMInformationCheck.HighAvailability.Contains($HighAvailability)) -or (($_CollectVMInformationCheck.HighAvailability.Contains($HighAvailability)) -and ($_CollectVMInformationCheck.HighAvailabilityAgent.Contains($HighAvailabilityAgent)))) { if (($_CollectVMInformationCheck.Feature -and $script:_InstalledFeatures.Contains($_CollectVMInformationCheck.Feature)) -or (-not $_CollectVMInformationCheck.Feature)) { if ((-not $RunLocally) -or ($RunLocally -and ($_CollectVMInformationCheck.RunInLocalMode))) { $_output = RunCommand -p $_CollectVMInformationCheck # check if result should be shown in report if ($_CollectVMInformationCheck.ShowInReport) { # create a new empty object per line $_outputarray_row = "" | Select-Object CheckID, Description, Output $_outputarray_row.CheckID = $_CollectVMInformationCheck.CheckID $_outputarray_row.Description = $_CollectVMInformationCheck.Description $_outputarray_row.Output = $_output -join ';;:;;' # add line to outputarray $_outputarray += $_outputarray_row } } } } } } # create HTML output $_outputarray = $_outputarray | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""VMInfo"">Collect VM Information</h2>This section collects basic information of the VM" $_outputarray = $_outputarray.Replace(";;:;;","<br/>") return $_outputarray } # collect additional VM infos function CollectVMInformationAdditional { $_outputarray_total = @() $_counter = 0 # looping through VM checks foreach ($_CollectVMInformationCheck in $_jsonconfig.VMCollectInformationAdditional) { $_outputarray = @() # check if CollechtVMInformation tasks needs to be executed against this OS and database if ( $_CollectVMInformationCheck.OS.Contains($VMOperatingSystem) -and ` $_CollectVMInformationCheck.DB.Contains($VMDatabase) -and ` $_CollectVMInformationCheck.Role.Contains($VMRole) -and ` ( $_CollectVMInformationCheck.OSVersion.Contains("all") -or $_CollectVMInformationCheck.OSVersion.Contains($VMOSRelease)) -and ` $_CollectVMInformationCheck.Hardwaretype.Contains($Hardwaretype)) { # check if check applies to HA or not and if HA check for HA-Agent if (($_CollectVMInformationCheck.HighAvailability.Contains($HighAvailability)) -or (($_CollectVMInformationCheck.HighAvailability.Contains($HighAvailability)) -and ($_CollectVMInformationCheck.HighAvailabilityAgent.Contains($HighAvailabilityAgent)))) { if ((-not $RunLocally) -or ($RunLocally -and ($_CollectVMInformationCheck.RunInLocalMode))) { if (($_CollectVMInformationCheck.Feature -and $script:_InstalledFeatures.Contains($_CollectVMInformationCheck.Feature)) -or (-not $_CollectVMInformationCheck.Feature)) { $_output = RunCommand -p $_CollectVMInformationCheck # check if result should be shown in report if ($_CollectVMInformationCheck.ShowInReport) { $_outputarray_row = "" | Select-Object Output $_outputarray_row.Output = $_output -join ';;:;;' $_outputarray += $_outputarray_row $_htmllink = "additionalinfo" + $_counter $_description = $_CollectVMInformationCheck.Description $_outputarray = $_outputarray | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""$_htmllink"">$_description</h2>" $script:_Content += "<a href=""#$_htmllink"">$_description</a><br>" $_outputarray = $_outputarray.Replace(";;:;;","<br/>") $_counter += 1 $_outputarray_total += $_outputarray } } } } } } return $_outputarray_total } # Run an OS command function RunCommand { [CmdletBinding()] param ( [object]$p ) if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Running " + $p.ProcessingCommand) } # command needs to run inside OS if ($p.CommandType -eq "OS") { # check if run in local mode if (-not $RunLocally) { if ($VMOperatingSystem -eq "Windows") { # Windows Invoke-Expression $p.ProcessingCommand } else { # Linux <# OLD SECTION OF Invoke-SSHCommand # root permissions required? if (($p.RootRequired) -and ($VMUsername -ne "root") -and (-not $LogonWithUserSSHKey) -and (-not $LogonAsRootSSHKey)) { # add sudo to the command $_command = "echo '$_ClearTextPassword' | sudo -E -S " + $p.ProcessingCommand # $_command = "printf '$_ClearTextPassword\n' | sudo -E -S " + $p.ProcessingCommand } else { if (($LogonWithUserSSHKey) -or ($LogonAsRootSSHKey -and ($VMUsername -ne "root")) ) { if ($p.RootRequired) { $_command = "sudo -E -S " + $p.ProcessingCommand } else { $_command = $p.ProcessingCommand } } else { # command will be used without sudo $_command = $p.ProcessingCommand } } if ($LogonWithUserSSHKey) { if ($_command.Contains("echo '$_ClearTextPassword' | ")) { Write-Host $_command $_command -replace "echo '$_ClearTextPassword' | ", "" Write-Host $_command } } # run the command $_result = Invoke-SSHCommand -Command $_command -SessionId $script:_SessionID # certain versions of Linux come back with sudo on standard error, removing the string $_result_ERROR = $_result.ERROR.replace("[sudo] password for ${VMUsername}: ","") # if result has errors, log them #if ($_result_ERROR -ne "") # if ($_result.ERROR -ne "") if (-not [string]::IsNullOrEmpty($_result_ERROR)) { WriteRunLog -category "ERROR" -message ($p.CheckID + " " + $_result_ERROR) } # just store theoutput of the command in $_result $_result = $_result.Output #> # run the command $_command = $p.ProcessingCommand # $_result = Invoke-SSHCommandStream -Command $_command -SSHSession $script:_sshsession # shell stream command ## start # $_result = Invoke-SSHStreamShellCommand -ShellStream $script:_sshstream -Command $_command # Check the result and see if it's an array and if the first object in the array starts with '<' # if so, then drop it from the array. # This fixes an issue with some Linux commands that return the command as the first object in the array #if ($_result -is [array]) { # if ($_result[0].StartsWith("<")) { # $_result = $_result[1..($_result.Length-1)] # } #} ## end ## start of new section using read and writeline # empty stream # $_result = $script:_sshstream.read() $script:_sshstream.read() | Out-Null # send command WriteDetailedDebugLog("Command: ") WriteDetailedDebugLog($_command) $script:_sshstream.WriteLine($_command) while (!$script:_sshstream.DataAvailable) { if (!$p.LongRunningCommand) { Start-Sleep -Milliseconds ($script:_sshstreamwait * 3) } else { Start-Sleep -Milliseconds ($script:_sshstreamwait * 10) } WriteDetailedDebugLog("Waiting...") } $_loop = 0 $_result = "" do { $_loop += 1 WriteDetailedDebugLog("Loop: $_loop") if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Loop: $_loop") } $_result += $script:_sshstream.read() # wait a little more if (!$p.LongRunningCommand) { Start-Sleep -Milliseconds ($script:_sshstreamwait * 2) } else { Start-Sleep -Milliseconds ($script:_sshstreamwait * 5) } } while ($script:_sshstream.DataAvailable) WriteDetailedDebugLog("Result:") WriteDetailedDebugLog($_result) # create an array from multiline string $_result = $_result.Split("`n") # remove CR from result just to make sure required since streams come back with CR if (-not [string]::IsNullOrEmpty($_result)) { $_result = $_result.Replace("`r","") } else { $_result = "" } # remove first line as it is the command itself and last line as it is the prompt if ($_result -is [array]) { # if ($_result[0].StartsWith("<")) { $_result = $_result[1..($_result.Length-2)] #} } ## end of new section using read and writeline # if postprocessingcommand is defined in JSON if (($p.PostProcessingCommand -ne "") -or ($p.PostProcessingCommand)) { # run postprocessing command $_command = $p.PostProcessingCommand $_command = $_command -replace "PARAMETER",$_result $_result = Invoke-Expression $_command } # store the result in script variable to access it for alternative output in JSON $script:_CommandResult = $_result WriteDetailedDebugLog("Final Result:") WriteDetailedDebugLog($_result) # return result return $_result } } else { try { # run command locally $_result = Invoke-Expression $p.ProcessingCommand # if postprocessingcommand is defined in JSON if (($p.PostProcessingCommand -ne "") -or ($p.PostProcessingCommand)) { # run postprocessing command $_command = $p.PostProcessingCommand $_command = $_command -replace "PARAMETER",$_result $_result = Invoke-Expression $_command } return $_result } catch { WriteRunLog -category "ERROR" -message ("Running command " + $p.ProcessingCommand) } } } # command is a PowerShell command (e.g. query Azure resources) if ($p.CommandType -eq "PowerShell") { # set command $_command = $p.ProcessingCommand # run command $_result = Invoke-Expression $_command # if postprocessingcommand is defined in JSON if (($p.PostProcessingCommand -ne "") -or ($p.PostProcessingCommand)) { # run postprocessing command $_command = $p.PostProcessingCommand $_command = $_command -replace "PARAMETER",$_result $_result = Invoke-Expression $_command } # return result return $_result } } # check if there is connectivity to Azure using Get-AzVM command function CheckAzureConnectivity { # check if connected to Azure if ($SubscriptionId) { $_SubscriptionInfo = Get-AzSubscription -SubscriptionId $SubscriptionId } else { $_context = Get-AzContext $SubscriptionId = $_context.Subscription.Id $_SubscriptionInfo = Get-AzSubscription -SubscriptionId $SubscriptionId } # if $_SubscritpionInfo then it got subscriptions if ($_SubscriptionInfo) { # check if connected to right subscription $_VMinfo = Get-AzVM -ResourceGroupName $AzVMResourceGroup -Name $AzVMName -ErrorAction SilentlyContinue if ($_VMinfo) { # connected to Azure $_ContextInfo = Get-AzContext $script:_SubscriptionID = $_ContextInfo.Subscription $script:_SubscriptionName = $_ContextInfo.Name $script:_VMName = $_VMinfo.Name $script:_CheckAzureConnectivity = $true } else { WriteRunLog -category "ERROR" -message "Unable to find resource group or VM, please check if you are connected to the correct subscription or if you had a typo" $script:_CheckAzureConnectivity = $false exit } } else { WriteRunLog -category "ERROR" -message "Please connect to Azure using the Connect-AzAccount command, if you are connected use the Select-AzSubscription command to set the correct context" $script:_CheckAzureConnectivity = $false exit } } # function to manually create an object for Run-Command function function PrepareCommand { [CmdletBinding()] param ( [string]$Command, [ValidateSet("OS","PowerShell")][string]$CommandType = "OS", [boolean]$RootRequired = $true, [string]$PostProcessingCommand = "", [boolean]$LongRunningCommand = $false ) $_p = "" | Select-Object ProcessingCommand, CommandType, RootRequired, PostProcessingCommand, LongRunningCommand $_p.ProcessingCommand = $Command $_p.CommandType = $CommandType $_p.RootRequired = $RootRequired $_p.PostProcessingCommand = $PostProcessingCommand $_p.LongRunningCommand = $LongRunningCommand return $_p } # Calculate Disk Name function CalculateDiskTypeSKU { param ( [int]$size, [string]$tier, [int]$iops ) # check which storage tier is used $_performancetype = switch ($tier) { Premium_LRS { 'P' } UltraSSD_LRS { 'U' } Standard_LRS { 'S' } StandardSSD_LRS { 'E' } PremiumV2_LRS { 'Pv2' } Default {} } # calculate disk SKU based on size $_sizetype = switch ($size) { ({$PSItem -le 4}) {'1'; break} ({$PSItem -le 8}) {'2'; break} ({$PSItem -le 16}) {'3'; break} ({$PSItem -le 32}) {'4'; break} ({$PSItem -le 64}) {'6'; break} ({$PSItem -le 128}) {'10'; break} ({$PSItem -le 256}) {'15'; break} ({$PSItem -le 512}) {'20'; break} ({$PSItem -le 1024}) {'30'; break} ({$PSItem -le 2048}) {'40'; break} ({$PSItem -le 4096}) {'50'; break} ({$PSItem -le 8192}) {'60'; break} ({$PSItem -le 16384}) {'70'; break} ({$PSItem -le 32768}) {'80'; break} ({$PSItem -ge 32769}) {'90'; break} } if ($_performancetype -eq "Pv2") { $_disksku = $_performancetype + "-" + $size + "GB-" + $iops + "IOPS" } else { $_disksku = $_performancetype + $_sizetype } return $_disksku } # Show storage configuration of VM function CollectVMStorage { $script:_DiskPerformance = @( [pscustomobject]@{StorageTier="Premium_LRS";Name="P1";Size=4;MBPS=25;IOPS=120}, [pscustomobject]@{StorageTier="Premium_LRS";Name="P2";Size=8;MBPS=25;IOPS=120}, [pscustomobject]@{StorageTier="Premium_LRS";Name="P3";Size=16;MBPS=25;IOPS=120}, [pscustomobject]@{StorageTier="Premium_LRS";Name="P4";Size=32;MBPS=25;IOPS=120}, [pscustomobject]@{StorageTier="Premium_LRS";Name="P6";Size=64;MBPS=50;IOPS=240}, [pscustomobject]@{StorageTier="Premium_LRS";Name="P10";Size=128;MBPS=100;IOPS=500}, [pscustomobject]@{StorageTier="Premium_LRS";Name="P15";Size=256;MBPS=125;IOPS=1100}, [pscustomobject]@{StorageTier="Premium_LRS";Name="P20";Size=512;MBPS=150;IOPS=2300}, [pscustomobject]@{StorageTier="Premium_LRS";Name="P30";Size=1024;MBPS=200;IOPS=5000}, [pscustomobject]@{StorageTier="Premium_LRS";Name="P40";Size=2048;MBPS=250;IOPS=7500}, [pscustomobject]@{StorageTier="Premium_LRS";Name="P50";Size=4096;MBPS=250;IOPS=7500}, [pscustomobject]@{StorageTier="Premium_LRS";Name="P60";Size=8192;MBPS=500;IOPS=16000}, [pscustomobject]@{StorageTier="Premium_LRS";Name="P70";Size=16384;MBPS=750;IOPS=18000}, [pscustomobject]@{StorageTier="Premium_LRS";Name="P80";Size=32767;MBPS=900;IOPS=20000}, [pscustomobject]@{StorageTier="StandardSSD_LRS";Name="E1";Size=4;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardSSD_LRS";Name="E2";Size=8;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardSSD_LRS";Name="E3";Size=16;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardSSD_LRS";Name="E4";Size=32;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardSSD_LRS";Name="E6";Size=64;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardSSD_LRS";Name="E10";Size=128;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardSSD_LRS";Name="E15";Size=256;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardSSD_LRS";Name="E20";Size=512;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardSSD_LRS";Name="E30";Size=1024;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardSSD_LRS";Name="E40";Size=2048;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardSSD_LRS";Name="E50";Size=4096;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardSSD_LRS";Name="E60";Size=8192;MBPS=400;IOPS=2000}, [pscustomobject]@{StorageTier="StandardSSD_LRS";Name="E70";Size=16384;MBPS=600;IOPS=4000}, [pscustomobject]@{StorageTier="StandardSSD_LRS";Name="E80";Size=32767;MBPS=750;IOPS=6000}, [pscustomobject]@{StorageTier="StandardHDD_LRS";Name="S4";Size=32;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardHDD_LRS";Name="S6";Size=64;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardHDD_LRS";Name="S10";Size=128;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardHDD_LRS";Name="S15";Size=256;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardHDD_LRS";Name="S20";Size=512;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardHDD_LRS";Name="S30";Size=1024;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardHDD_LRS";Name="S40";Size=2048;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardHDD_LRS";Name="S50";Size=4096;MBPS=60;IOPS=500}, [pscustomobject]@{StorageTier="StandardHDD_LRS";Name="S60";Size=8192;MBPS=300;IOPS=1300}, [pscustomobject]@{StorageTier="StandardHDD_LRS";Name="S70";Size=16384;MBPS=500;IOPS=2000}, [pscustomobject]@{StorageTier="StandardHDD_LRS";Name="S80";Size=32767;MBPS=500;IOPS=2000} ) if ($VMOperatingSystem -eq "Windows") { # future windows support } else { if (-not $RunLocally) { # get VM info $script:_VMinfo = Get-AzVM -ResourceGroupName $AzVMResourceGroup -Name $AzVMName $script:_DiskControllerType = $script:_VMinfo.StorageProfile.DiskControllerType } else { $_command = PrepareCommand -Command "ls -l /dev/nvme* 2>/dev/null | wc -l" $_number_of_NVMe_disks = RunCommand -p $_command if ([int]$_number_of_NVMe_disks -gt 0) { $script:_DiskControllerType = "NVMe" } else { $script:_DiskControllerType = "SCSI" } } # collect LVM configuration $_command = PrepareCommand -Command "/sbin/lvm fullreport --reportformat json" $script:_lvmconfig = RunCommand -p $_command | ConvertFrom-Json # get storage using metadata service $_command = PrepareCommand -Command "/usr/bin/curl -s --noproxy '*' -H Metadata:true 'http://169.254.169.254/metadata/instance/compute/storageProfile?api-version=2021-12-13';echo" $script:_azurediskconfig = RunCommand -p $_command | ConvertFrom-Json if ($script:_DiskControllerType -eq "SCSI") { # SCSI part # get device for root $_command = PrepareCommand -Command "realpath /dev/disk/azure/root" -CommandType "OS" # $_command = PrepareCommand -Command "realpath -m /dev/disk/cloud/azure_root" -CommandType "OS" $_rootdisk = RunCommand -p $_command if ($_rootdisk.Contains("/dev/sd")) { # SCSI Disk found } else { $_command = PrepareCommand -Command "realpath -m /dev/disk/cloud/azure_root" -CommandType "OS" $_rootdisk = RunCommand -p $_command if ($_rootdisk.Contains("/dev/mapper")) { # OS disk is running LVM $_rootdisk = ($script:_lvmconfig.report | Where-Object {$_.lv.lv_dm_path -like $_rootdisk}).pv[0].pv_name # remove partition number $_rootdisk = $_rootdisk -replace '\d+','' } else { # backup if the virtual device doesn't exist $_rootdisk = "/dev/sda" } } #if ($_rootdisk -eq "/dev/disk/cloud/azure_root") { # backup if the virtual device doesn't exist # $_rootdisk = "/dev/sda" #} } else { # NVMe part $_command = PrepareCommand -Command "findmnt -n -o SOURCE /" -CommandType "OS" $_rootdisk = RunCommand -p $_command } # Azure Resource Disks are SCSI, even OS and data disk are NVMe # get device for resource disk # $_command = PrepareCommand -Command "realpath /dev/disk/azure/resource" -CommandType "OS" if ($script:_azurediskconfig.resourceDisk.size -gt 0) { $_command = PrepareCommand -Command "realpath -m /dev/disk/cloud/azure_resource" -CommandType "OS" $_resourcedisk = RunCommand -p $_command if ($_resourcedisk -eq "/dev/disk/cloud/azure_resource") { # backup if the virtual device doesn't exist $_resourcedisk = "/dev/sdb" } } else { # setting a value for systems that don't have a resource disk for lsscsi grep command $_resourcedisk = "/dev/noresourcedisk" } if (-not $RunLocally) { # get Azure Disks in Resource Group $_command = PrepareCommand -Command "Get-AzDisk -ResourceGroupName $AzVMResourceGroup" -CommandType "PowerShell" $script:_AzureDiskDetails = RunCommand -p $_command } $script:_AzureDisks = @() # add OS Disk Infos $_AzureDisk_row = "" | Select-Object LUNID, Name, DeviceName, VolumeGroup, Size, DiskType, IOPS, MBPS, PerformanceTier, StorageType, Caching, WriteAccelerator $_AzureDisk_row.LUNID = "OsDisk" $_AzureDisk_row.Name = $script:_azurediskconfig.osDisk.name $_AzureDisk_row.Size = $script:_azurediskconfig.osDisk.DiskSizeGB $_AzureDisk_row.StorageType = $script:_azurediskconfig.osDisk.managedDisk.storageAccountType $_AzureDisk_row.Caching = $script:_azurediskconfig.osDisk.caching $_AzureDisk_row.WriteAccelerator = $script:_azurediskconfig.osDisk.writeAcceleratorEnabled $_AzureDisk_row.DiskType = CalculateDiskTypeSKU -size $script:_azurediskconfig.osDisk.DiskSizeGB -tier $script:_azurediskconfig.osDisk.managedDisk.storageAccountType if (-not $RunLocally) { $_AzureDisk_row.IOPS = ($script:_AzureDiskDetails | Where-Object { $_.Name -eq $script:_azurediskconfig.osDisk.name }).DiskIOPSReadWrite $_AzureDisk_row.MBPS = ($script:_AzureDiskDetails | Where-Object { $_.Name -eq $script:_azurediskconfig.osDisk.name }).DiskMBpsReadWrite $_AzureDisk_row.PerformanceTier = ($script:_AzureDiskDetails | Where-Object { $_.Name -eq $script:_azurediskconfig.osDisk.name }).Tier } else { $_AzureDisk_row.IOPS = ($script:_DiskPerformance | Where-Object { ($_.Size -eq $_AzureDisk_row.Size) -and ($_.StorageTier -eq $_AzureDisk_row.StorageType) }).IOPS $_AzureDisk_row.MBPS = ($script:_DiskPerformance | Where-Object { ($_.Size -eq $_AzureDisk_row.Size) -and ($_.StorageTier -eq $_AzureDisk_row.StorageType) }).MBPS } # $_AzureDisk_row.DeviceName = ($script:_diskmapping | Where-Object { ($_.P5 -eq 0) -and ($_.P2 -eq $script:_OSDiskSCSIControllerID) }).P7 $_AzureDisk_row.DeviceName = $_rootdisk try { # $_AzureDisk_row.VolumeGroup = ($script:_lvmconfig.report | Where-Object {$_.pv.pv_name -like ($_AzureDisk_row.DeviceName + "*")}).vg[0].vg_name $_AzureDisk_row.VolumeGroup = ($script:_lvmconfig.report | Where-Object {$_.pv.pv_name -like ($_AzureDisk_row.DeviceName + "*")}).vg[0].vg_name } catch { if (-not $RunLocally) { WriteRunLog -category "INFO" -message ("Couldn't find Volume Group for device " + $_AzureDisk_row.DeviceName) } $_AzureDisk_row.VolumeGroup = "novg-" + $_AzureDisk_row.DeviceName.Replace("/dev/","") } $script:_AzureDisks += $_AzureDisk_row # get sg_map output for LUN-ID to disk mapping $_rootdisk.Replace("/dev/","") $_resourcedisk.Replace("/dev/","") #$_sgmap_command = "sg_map -i -x | grep Virtual | grep -v " + $_rootdisk + " | grep -v " + $_resourcedisk #$_command = PrepareCommand -Command $_sgmap_command -CommandType "OS" #$script:_diskmapping = RunCommand -p $_command #$script:_diskmapping = ConvertFrom-String_sgmap -p $script:_diskmapping if ($script:_DiskControllerType -eq "SCSI") { # SCSI part $_lsscsi_command = "lsscsi | sed 's/\[//; s/\]//; s/\.//' | sed 's/:/ /g' | grep Virtual | grep -v '" + $_rootdisk + " ' | grep -v '" + $_resourcedisk + " ' | grep -v 'cd/dvd'" $_command = PrepareCommand -Command $_lsscsi_command -CommandType "OS" $script:_diskmapping = RunCommand -p $_command $script:_diskmapping = ConvertFrom-String_lsscsi -p $script:_diskmapping } else { # NVMe part # old NVMe command, Red Hat doesn't show MSFT devices, going through block instead to get NSID (Namespace ID) which is always LUN-ID + 2 # $_nvme_command = 'for nvmedev in /dev/disk/by-id/nvme-MSFT_NVMe_Accelerator_v1.0_SN:_000001_*; do if [[ $nvmedev != *"part"* ]]; then real=$(realpath $nvmedev); lunid=$(echo $nvmedev | cut -d "_" -f 7); lunid=$((lunid-2)); if [[ $lunid -ge 0 ]]; then echo $lunid","$real; fi; fi; done' $_nvme_command = 'for nvmedev in /sys/block/nvme0n*; do device=$(echo $nvmedev | cut -d "/" -f 4); lunid=$(cat $nvmedev/nsid); lunid=$((lunid-2)); if [[ $lunid -ge 0 ]]; then echo $lunid",/dev/"$device; fi; done' $_command = PrepareCommand -Command $_nvme_command -CommandType "OS" $script:_diskmapping = RunCommand -p $_command $script:_diskmapping = $script:_diskmapping | ConvertFrom-Csv -Delimiter "," -Header LUN,Device } # add datadisks to table foreach ($_datadisk in $script:_azurediskconfig.dataDisks) { # create empty object $_AzureDisk_row = "" | Select-Object LUNID, Name, DeviceName, VolumeGroup, Size, DiskType, IOPS, MBPS, PerformanceTier, StorageType, Caching, WriteAccelerator # add disk details $_AzureDisk_row.LUNID = $_datadisk.lun $_AzureDisk_row.Name = $_datadisk.name $_AzureDisk_row.Size = $_datadisk.DiskSizeGB $_AzureDisk_row.StorageType = $_datadisk.managedDisk.storageAccountType $_AzureDisk_row.Caching = $_datadisk.caching $_AzureDisk_row.WriteAccelerator = $_datadisk.writeAcceleratorEnabled # $_AzureDisk_row.DeviceName = ($script:_diskmapping | Where-Object { ($_.P5 -eq $_datadisk.lun) -and ($_.P2 -eq $script:_DataDiskSCSIControllerID) }).P7 # $_AzureDisk_row.DeviceName = ($script:_diskmapping | Where-Object { ($_.P5 -eq $_datadisk.lun) }).P7 if ($script:_DiskControllerType -eq "SCSI") { # SCSI part try { $_AzureDisk_row.DeviceName = ($script:_diskmapping | Where-Object { ($_.P4 -eq $_datadisk.lun) }).P10 } catch { WriteRunLog -category "WARNING" -message ("Couldn't find device name for LUN " + $_datadisk.lun) } } else { # NVMe part try { $_AzureDisk_row.DeviceName = ($script:_diskmapping | Where-Object { ($_.LUN -eq $_datadisk.lun) }).Device } catch { WriteRunLog -category "WARNING" -message ("Couldn't find device name for LUN " + $_datadisk.lun) } } # checking for VG, direct device name (no partition) try { $_AzureDisk_row.VolumeGroup = ($script:_lvmconfig.report | Where-Object {$_.pv.pv_name -eq $_AzureDisk_row.DeviceName}).vg[0].vg_name } catch { } try { if (-not $_AzureDisk_row.VolumeGroup) { $_AzureDisk_row.VolumeGroup = ($script:_lvmconfig.report | Where-Object {$_.pv.pv_name -like ($_AzureDisk_row.DeviceName + "*")}).vg[0].vg_name } } catch { if (-not $RunLocally) { WriteRunLog -category "INFO" -message ("Couldn't find Volume Group for device " + $_AzureDisk_row.DeviceName) } $_AzureDisk_row.VolumeGroup = "novg-" + $_AzureDisk_row.DeviceName.Replace("/dev/","") } if (-not $RunLocally) { $_AzureDisk_row.IOPS = ($script:_AzureDiskDetails | Where-Object { $_.Name -eq $_datadisk.name }).DiskIOPSReadWrite $_AzureDisk_row.MBPS = ($script:_AzureDiskDetails | Where-Object { $_.Name -eq $_datadisk.name }).DiskMBpsReadWrite $_AzureDisk_row.PerformanceTier = ($script:_AzureDiskDetails | Where-Object { $_.Name -eq $_datadisk.name }).Tier } else { $_AzureDisk_row.IOPS = ($script:_DiskPerformance | Where-Object { ($_.Size -eq $_AzureDisk_row.Size) -and ($_.StorageTier -eq $_AzureDisk_row.StorageType) }).IOPS $_AzureDisk_row.MBPS = ($script:_DiskPerformance | Where-Object { ($_.Size -eq $_AzureDisk_row.Size) -and ($_.StorageTier -eq $_AzureDisk_row.StorageType) }).MBPS } $_AzureDisk_row.DiskType = CalculateDiskTypeSKU -size $_datadisk.DiskSizeGB -tier $_datadisk.managedDisk.storageAccountType -iops $_AzureDisk_row.IOPS $script:_AzureDisks += $_AzureDisk_row } # convert output to HTML $script:_azurediskOutput = $script:_AzureDisks | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""AzureDisks"">Azure Disks</h2>" # add LVM Groups to HTML index $script:_Content += "<a href=""#AzureDisks"">Azure Disks</a><br>" return $script:_azurediskOutput } } # get LVM groups (VGs) function CollectLVMGroups { if ($VMOperatingSystem -eq "Windows") { # Display the cluster nodes on the system $_winclusternodes = Invoke-Command -ComputerName $AzVMName -ScriptBlock {Get-ClusterResource} # create HTML output $_winclusternodesOutput = $_winclusternodes |Select-Object Cluster, State, OwnerGroup, OwnerNode, ResourceType, MaintenanceMode| ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""Cluster Nodes"">Cluster Nodes</h2>This section shows you the Windows Cluster Nodes available on the VM." # add entry in HTML index $script:_Content += "<a href=""#Cluster Nodes"">Cluster Nodes</a><br>" return $_winclusternodesOutput } else { # create empty object $script:_lvmgroups = @() # loop through LVM report foreach ($_lvmgroup in $script:_lvmconfig.report) { # create empty object for line $_lvmgroup_row = "" | Select-Object Name,Disks,LogicalVolumes,Totalsize,TotalIOPS,TotalMBPS # add data to object $_lvmgroup_row.Name = $_lvmgroup.vg.vg_name $_lvmgroup_row.Disks = $_lvmgroup.vg.pv_count $_lvmgroup_row.LogicalVolumes = $_lvmgroup.vg.lv_count $_lvmgroup_row.Totalsize = $_lvmgroup.vg.vg_size $_lvmgroup_row.TotalIOPS = ($script:_AzureDisks | Where-Object { $_.VolumeGroup -eq $_lvmgroup.vg.vg_name } | Measure-Object -Property IOPS -Sum).Sum $_lvmgroup_row.TotalMBPS = ($script:_AzureDisks | Where-Object { $_.VolumeGroup -eq $_lvmgroup.vg.vg_name } | Measure-Object -Property MBPS -Sum).Sum $script:_lvmgroups += $_lvmgroup_row } } # convert output to HTML $script:_lvmgroupsOutput = $script:_lvmgroups | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""LVMGroups"">Collect LVM Groups Information</h2>" # add LVM Groups to HTML index $script:_Content += "<a href=""#LVMGroups"">LVM Groups</a><br>" return $script:_lvmgroupsOutput } # collect logical volumes function CollectLVMVolummes { # create empty object $script:_lvmvolumes = @() # loop through LVM config foreach ($_lvmgroup in $script:_lvmconfig.report) { # only go for VGs that are not rootvg if ($_lvmgroup.vg.vg_name -ne "rootvg") { foreach ($_lvmvolume in $_lvmgroup.lv) { # create empty object for data line $_lvmvolume_row = "" | Select-Object Name,VGName,LVPath,DMPath,Layout,Size,Stripesize,Stripes # add data $_lvmvolume_row.Name = $_lvmvolume.lv_name $_lvmvolume_row.VGName = $_lvmgroup.vg.vg_name $_lvmvolume_row.LVPath = $_lvmvolume.lv_path $_lvmvolume_row.DMPath = $_lvmvolume.lv_dm_path $_lvmvolume_row.Layout = $_lvmvolume.lv_layout $_lvmvolume_row.Size = $_lvmvolume.lv_size $_lvmvolume_row.StripeSize = $_lvmgroup.seg[0].stripe_size # $_lvmvolume_row.Stripes = ($_lvmgroup.seg.stripes | Measure-Object -Sum).Count $_lvmvolume_row.Stripes = $_lvmgroup.seg[0].stripes # add line to report $script:_lvmvolumes += $_lvmvolume_row } } } # convert output to HTML $script:_lvmvolumesOutput = $script:_lvmvolumes | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""LVMVolumes"">Collect LVM Volume Information</h2>" # add entry to HTML index $script:_Content += "<a href=""#LVMVolumes"">LVM Volumes</a><br>" $script:_Content += "<a href=""#LogicalDisks"">Logical Disks</a><br>" return $script:_lvmvolumesOutput } # collect network interfaces function CollectNetworkInterfaces { # get Azure VM infos $_VMinfo = Get-AzVM -ResourceGroupName $AzVMResourceGroup -Name $AzVMName -ErrorAction SilentlyContinue # create empty object for network interfaces $script:_NetworkInterfaces = @() # loop through network interfaces foreach ($_VMnetworkinterface in $_VMinfo.NetworkProfile.NetworkInterfaces) { # get details for each network interface $_networkinterface = Get-AzNetworkInterface -ResourceId $_VMnetworkinterface.Id # create empty object for each line $_networkinterface_row = "" | Select-Object Name,AcceleratedNetworking,IPForwarding,PrivateIP,NSG # add infos $_networkinterface_row.Name = $_networkinterface.Name $_networkinterface_row.AcceleratedNetworking = $_networkinterface.EnableAcceleratedNetworking $_networkinterface_row.IPForwarding = $_networkinterface.EnableIPForwarding $_networkinterface_row.NSG = $_networkinterface.NetworkSecurityGroup.Id # add private IP addresses for interfaces $_networkinterface_row.PrivateIP = "" foreach ($_ipconfig in $_networkinterface.IpConfigurations) { $_networkinterface_row.PrivateIP = $_networkinterface_row.PrivateIP + $_ipconfig.PrivateIpAddress + " " } # add output to object $script:_NetworkInterfaces += $_networkinterface_row } # create HTML output $script:_NetworkInterfacesOutput = $script:_NetworkInterfaces | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""NetworkInterfaces"">Collect Network Interfaces</h2>" # create entry in HTML index $script:_Content += "<a href=""#NetworkInterfaces"">Network Interfaces</a><br>" return $script:_NetworkInterfacesOutput } # collect load balancers function CollectLoadBalancer { # get Azure VM Info $_VMinfo = Get-AzVM -ResourceGroupName $AzVMResourceGroup -Name $AzVMName -ErrorAction SilentlyContinue # create empty object for load balancers $Script:_LoadBalancers = @() # loop through each network interface foreach ($_VMnetworkinterface in $_VMinfo.NetworkProfile.NetworkInterfaces) { # get network interface details $_networkinterface = Get-AzNetworkInterface -ResourceId $_VMnetworkinterface.Id # loop through IP configurations of each interface foreach ($_ipconfig in $_networkinterface.IpConfigurations) { # loop through each loadbalancer backend address pool of each interface IP config foreach ($_loadbalancerbackendpool in $_ipconfig.LoadBalancerBackendAddressPools) { # create empty load balancer row entry $_loadbalancer_row = "" | Select-Object Name,Type,IdleTimeout,FloatingIP,Protocols,ProbeThreshold,ProbeInterval # split data from pool to (full resource string) for LB name and Resource Group $_loadbalancername = ($_loadbalancerbackendpool.id).Split("/")[8] $_loadbalancerresourcegroup = ($_loadbalancerbackendpool.id).Split("/")[4] # get details for load balancer $_loadbalancer = Get-AzLoadBalancer -Name $_loadbalancername -ResourceGroupName $_loadbalancerresourcegroup if ($_loadbalancer.LoadBalancingRules) { # add details $_loadbalancer_row.Name = $_loadbalancername $_loadbalancer_row.Type = ($_loadbalancer.SkuText | ConvertFrom-JSON).Name $_loadbalancer_row.IdleTimeout = $_loadbalancer.LoadBalancingRules[0].IdleTimeoutInMinutes $_loadbalancer_row.FloatingIP = $_loadbalancer.LoadBalancingRules[0].EnableFloatingIP $_loadbalancer_row.Protocols = $_loadbalancer.LoadBalancingRules[0].Protocol if ($_loadbalancer.Probes -ne $null -and $_loadbalancer.Probes.Count -gt 0) { $_loadbalancer_row.ProbeThreshold = $_loadbalancer.Probes[0].ProbeThreshold $_loadbalancer_row.ProbeInterval = $_loadbalancer.Probes[0].IntervalInSeconds } # add data to table $Script:_LoadBalancers += $_loadbalancer_row } } } } # if load balancer found if ($Script:_LoadBalancers) { $_LoadBalancerOutput = $script:_LoadBalancers | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""LoadBalancers"">Collect Load Balancer</h2>" } else { # no load balancer found $_loadbalancer_row = "" | Select-Object "Description" $_loadbalancer_row.Description = "No load balancer assigned to network interfaces" $_LoadBalancerOutput = $_loadbalancer_row | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""LoadBalancers"">Collect Load Balancer</h2>" } $script:_Content += "<a href=""#LoadBalancers"">Load Balancer</a><br>" return $_LoadBalancerOutput } # calculate a kernel version function CalculateKernelVersion { Param ( [string] $kernelversion ) # Linux Kernel Version consist of - and . # this module generates a number to compare different kernel versions # reason for the module is that there is no built in kernel comparison function and the kernel names are different on distributions # the idea is to take ever number and multiply it with a factor, the more right you get on kernel versions, the smaller the factor is # e.g. a kernel 4.2.65 would generate # 4 * 10000000 + 2 * 100000 + 65 * 1000 # the result is 40265000 # this value is then used in a greater or lower than X comparison $_kversionarray = @() # replace "-" with "." $_kversion = $kernelversion.Replace("-",".") # now split every number into an array $_kversionarray = $_kversion.split(".") # calculate the kernel version number $_kversionnumber = [System.Int32]$_kversionarray[0] * 10000000 + [System.Int32]$_kversionarray[1] * 100000 + [System.Int32]$_kversionarray[2] * 1000 + [System.Int32]$_kversionarray[3] * 100 + [System.Int32]$_kversionarray[4] * 10 return $_kversionnumber } # check for kernel version function CheckForKernelVersion { Param ( [string] $startversion, [string] $endversion, [string] $version ) # this function used the calculate kernel version function to create an integer value for comparison # start kernel version of condition $_kversionnumberstart = CalculateKernelVersion -kernelversion $startversion # end kernel version of condition $_kversionnumberend = CalculateKernelVersion -kernelversion $endversion # used kernel version $_kversionnumber = CalculateKernelVersion -kernelversion $version # check if the kernel version applies if (($_kversionnumber -gt $_kversionnumberstart) -and ($_kversionnumber -lt $_kversionnumberend)) { # yes, condition met return $true } else { # check doesn't apply return $false } } # function to remove unnessecary tabs and spaces function RemoveTabsAndSpaces { param ( [string]$OriginalString ) $_newstring = $OriginalString # remove tabs $_newstring = $_newstring -replace '\t',' ' # remove double spaces while ($_newstring -contains " ") { $_newstring = $_newstring -replace ' ', ' ' } return $_newstring } # function to create a line per check, required to add e.g. docs URL or SAP note entries function AddCheckResultEntry { param ( [string]$CheckID="NoCheckID", [string]$Description="", [string]$AdditionalInfo="", [string]$TestResult="", [string]$ExptectedResult="", [string]$Status="", [string]$SAPNote="", [string]$MicrosoftDocs="", [string]$ErrorCategory="" ) # create empty object per line if (-not $RunLocally) { $_Check_row = "" | Select-Object CheckID, Description, Testresult, ExpectedResult, Status, SAPNote, MicrosoftDocs, AdditionalInfo } else { $_Check_row = "" | Select-Object CheckID, Description, Testresult, ExpectedResult, Status, SAPNote, MicrosoftDocs, Success, VmRole, AdditionalInfo } # add infos $_Check_row.CheckID = $CheckID $_Check_row.Description = $Description $_Check_row.AdditionalInfo = $AdditionalInfo $_Check_row.Testresult = $TestResult $_Check_row.ExpectedResult = $ExptectedResult if ($RunLocally) { $_Check_row.VmRole = $VMRole } # taking input from JSON and adding INFO, ERROR or WARNING if ($Status -eq "ERROR") { $_Check_row.Status = $ErrorCategory if ($RunLocally) { $_Check_row.Success = $false } } else { $_Check_row.Status = $Status if ($RunLocally) { $_Check_row.Success = $true } } # if SAPNote is defined it will add the HTML code for the link if ($SAPNote -ne "") { if (-not $RunLocally) { $_Check_row.SAPNote = "::SAPNOTEHTML1::" + $SAPNote + "::SAPNOTEHTML2::" + $SAPNote + "::SAPNOTEHTML3::" } else { $_Check_row.SAPNote = "$SAPNote" } } # if MicrosoftDocs is defined it will add HTML code for the link if ($MicrosoftDocs -ne "") { if (-not $RunLocally) { $_Check_row.MicrosoftDocs = "::MSFTDOCS1::" + $MicrosoftDocs + "::MSFTDOCS2::" + "Link" + "::MSFTDOCS3::" } else { $_Check_row.MicrosoftDocs = "$MicrosoftDocs" } } # add data to checks $script:_Checks += $_Check_row } # run the quality checks (compare expectations with real values) function RunQualityCheck { # add empty object for all checks done $script:_Checks = @() # add empty storage type object (will be filled to know if check applies) $script:_StorageType = @() # adding premium storage for app and ASCS nodes if ($VMRole -eq "ASCS" -or $VMRole -eq "APP") { $script:_StorageType += "Premium_LRS" } # adding premium storage for app and ASCS nodes if ($VMRole -eq "DB" -and $VMDatabase -ne "HANA") { $script:_StorageType += "Premium_LRS" } # STORAGE CHECKS SAP HANA # checking for data disks if (($VMDatabase -eq "HANA") -and ($VMRole -eq "DB")) { if ($SID) { WriteRunLog -message "Searching for directories for SID $SID" -category "INFO" # get directory for /hana/shared by checking /usr/sap/SID/HDB00 directory $_command = PrepareCommand -Command "if [ -d /usr/sap/$SID/HDB?? ]; then echo 0; else echo 1; fi" $_hanashared_exists = RunCommand -p $_command if ($_hanashared_exists -eq "0") { $_command = PrepareCommand -Command "findmnt -T /usr/sap/$SID/HDB?? | tail -n +2" -CommandType "OS" -RootRequired $true $script:_persistance_hanashared = RunCommand -p $_command try { $script:_persistance_hanashared = ConvertFrom-String_findmnt -p $script:_persistance_hanashared $_hanashared_filesystems = $script:_persistance_hanashared.target } catch { WriteRunLog -message "couldn't find directory for SID $SID" -category "WARNING" } WriteRunLog -message "checking for HANA global.ini" -category "INFO" $_command = PrepareCommand -Command "if test -f /usr/sap/$SID/SYS/global/hdb/custom/config/global.ini; then echo 0; else echo 1; fi" -CommandType "OS" -RootRequired $true $_globalini_exists = RunCommand -p $_command if ($_globalini_exists -eq "0") { WriteRunLog -message "found HANA global.ini" -category "INFO" try { # global.ini file exists, getting data # check config from global.ini $_command = PrepareCommand -Command "cat /usr/sap/$SID/SYS/global/hdb/custom/config/global.ini | grep basepath_datavolumes" -CommandType "OS" -RootRequired $true $script:_persistance_datavolumes = RunCommand -p $_command # check config from global.ini $_command = PrepareCommand -Command "cat /usr/sap/$SID/SYS/global/hdb/custom/config/global.ini | grep basepath_logvolumes" -CommandType "OS" -RootRequired $true $script:_persistance_logvolumes = RunCommand -p $_command # convert output from global.ini and split it, everything in [1] is the path $script:_persistance_datavolumes = ($script:_persistance_datavolumes.Split("=")[1]) -Replace " " $script:_persistance_logvolumes = ($script:_persistance_logvolumes.Split("=")[1]) -Replace " " } catch { # set default paths $script:_persistance_datavolumes = "/hana/data/" + $SID $script:_persistance_logvolumes = "/hana/log/" + $SID $script:_persistance_hanashared = "/hana/shared/" + $SID } } else { # set default paths WriteRunLog -message "HANA global.ini not found, fallback paths" -category "WARNING" $script:_persistance_datavolumes = "/hana/data" $script:_persistance_logvolumes = "/hana/log" $script:_persistance_hanashared = "/hana/shared" } # get all files for /hana/data $_commandstring = "find $_persistance_datavolumes -type f" $_command = PrepareCommand -Command $($_commandstring) -CommandType "OS" -RootRequired $true $script:_persistance_datavolumes_files = RunCommand -p $_command # get all files for /hana/log $_commandstring = "find $_persistance_logvolumes -type f" $_command = PrepareCommand -Command $($_commandstring) -CommandType "OS" -RootRequired $true $script:_persistance_logvolumes_files = RunCommand -p $_command # loop through all files and get the file systems they are using $_datavolumes_filesystems = @() foreach ($_datavolumes_file in $script:_persistance_datavolumes_files) { $_command = PrepareCommand -Command "findmnt -T $_datavolumes_file | tail -n +2" -CommandType "OS" -RootRequired $true $_findmnt_temp = RunCommand -p $_command $_findmnt_temp = ConvertFrom-String_findmnt -p $_findmnt_temp $_datavolumes_filesystems += $_findmnt_temp.target } # loop through all files and get the file systems they are using $_logvolumes_filesystems = @() foreach ($_logvolumes_file in $script:_persistance_logvolumes_files) { $_command = PrepareCommand -Command "findmnt -T $_logvolumes_file | tail -n +2" -CommandType "OS" -RootRequired $true $_findmnt_temp = RunCommand -p $_command $_findmnt_temp = ConvertFrom-String_findmnt -p $_findmnt_temp $_logvolumes_filesystems += $_findmnt_temp.target } # create a list of all file systems used (unique values) $_datavolumes_filesystems = $_datavolumes_filesystems | Select-Object -Unique $_logvolumes_filesystems = $_logvolumes_filesystems | Select-Object -Unique # add log entries WriteRunLog -message ("Found shared filesystem for SID " + $SID) WriteRunLog -message "$_hanashared_filesystems" -category "INFO" WriteRunLog -message ("Found data filesystems for SID " + $SID) -category "INFO" WriteRunLog -message "$_datavolumes_filesystems" -category "INFO" WriteRunLog -message ("Found log filesystems for SID " + $SID) -category "INFO" WriteRunLog -message "$_logvolumes_filesystems" -category "INFO" WriteRunLog -message "Setting new values for DBDataDir and DBLogDir" -category "INFO" # setting new script variables $script:DBDataDir = $_datavolumes_filesystems $script:DBLogDir = $_logvolumes_filesystems $script:DBSharedDir = $_hanashared_filesystems } else { WriteRunLog -message "Can't find /usr/sap/$SID" -category "WARNING" WriteRunLog -message "Continuing with default directories" -category "WARNING" } } # adding Premium_LRS as default disk type for script use $script:_StorageType += "Premium_LRS" # default URL for HANA storage documentation $_saphanastorageurl = "https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/hana-vm-operations-storage" ## getting file system for /hana/data $_filesystem_hana = ($script:_filesystems | Where-Object {$_.Target -in $script:DBDataDir}) if ([String]::IsNullOrEmpty($_filesystem_hana)){ # didn't find file system WriteRunLog -category "ERROR" -message "Couldn't find mounted file system $script:DBDataDir" if (-not $RunLocally) { exit } } if ($_filesystem_hana.Source.StartsWith("/dev/sd") -or $_filesystem_hana.Source.StartsWith("/dev/nvme0n")) { $_filesystem_hana_type = "direct" } else { $_filesystem_hana_type = "lvm" } if ( ($_filesystem_hana.fstype | Select-Object -Unique) -in @('xfs','nfs','nfs4')) { AddCheckResultEntry -CheckID "HDB-FS-0001" -Description "SAP HANA Data: File System" -TestResult $_filesystem_hana.fstype -ExptectedResult "xfs, nfs or nfs4" -Status "OK" -MicrosoftDocs $_saphanastorageurl -SAPNote "2972496" } else { AddCheckResultEntry -CheckID "HDB-FS-0001" -Description "SAP HANA Data: File System" -TestResult $_filesystem_hana.fstype -ExptectedResult "xfs, nfs or nfs4" -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -SAPNote "2972496" -ErrorCategory "ERROR" } if ( (($script:_filesystems | Where-Object {$_.target -in $script:DBDataDir}).MaxMBPS | Measure-Object -Sum).Sum -ge $_jsonconfig.HANAStorageRequirements.HANADataMBPS) { AddCheckResultEntry -CheckID "HDB-FS-0002" -Description "SAP HANA Data: Disk Performance" -TestResult ($script:_filesystems | Where-Object {$_.target -eq $script:DBDataDir}).MaxMBPS -ExptectedResult ">= 400 MByte/s" -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { AddCheckResultEntry -CheckID "HDB-FS-0002" -Description "SAP HANA Data: Disk Performance" -TestResult ($script:_filesystems | Where-Object {$_.target -eq $script:DBDataDir}).MaxMBPS -ExptectedResult ">= 400 MByte/s" -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } if ($_filesystem_hana.fstype -eq 'xfs') { # getting disks for /hana/data if ($_filesystem_hana_type -eq "lvm") { $_AzureDisks_hana = ($_AzureDisks | Where-Object {$_.VolumeGroup -in $_filesystem_hana.vg}) } else { $_AzureDisks_for_hanadata_filesystems = $script:_filesystems | Where-Object {$_.target -in $script:DBDataDir} # $_AzureDisks_hana = ($_AzureDisks | Where-Object { $_.DeviceName -in $_AzureDisks_for_hanadata_filesystems.Source}) $_AzureDisks_hana = ($_AzureDisks | Where-Object { $_AzureDisks_for_hanadata_filesystems.Source.Contains($_.DeviceName) }) } $_FirstDisk = $_AzureDisks_hana[0] # checking if IOPS need to be checked (Ultra Disk) if ($_FirstDisk.StorageType -eq "UltraSSD_LRS") { if ( ($_filesystem_hana.fstype | Select-Object -Unique) -in @('xfs')) { if ( (($script:_filesystems | Where-Object {$_.target -in $script:DBDataDir}).MaxIOPS | Measure-Object -Sum).Sum -ge $_jsonconfig.HANAStorageRequirements.HANADataIOPS) { AddCheckResultEntry -CheckID "HDB-FS-0003" -Description "SAP HANA Data: Disk Performance" -TestResult ($script:_filesystems | Where-Object {$_.target -eq $script:DBDataDir}).MaxIOPS -ExptectedResult ">= 7000 IOPS" -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { AddCheckResultEntry -CheckID "HDB-FS-0003" -Description "SAP HANA Data: Disk Performance" -TestResult ($script:_filesystems | Where-Object {$_.target -eq $script:DBDataDir}).MaxIOPS -ExptectedResult ">= 7000 IOPS" -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } } } # check if stripe size check required (no of disks greater than 1 in VG) and disk type is LVM if (($_AzureDisks_hana.count -gt 1) -and ($_filesystem_hana_type -eq "lvm")) { $_HANAStripeSize = $_jsonconfig.HANAStorageRequirements.HANADataStripeSize if ($_filesystem_hana.StripeSize -eq $_HANAStripeSize) { # stripe size correct AddCheckResultEntry -CheckID "HDB-FS-0004" -Description "SAP HANA Data: stripe size" -AdditionalInfo ("Disk " + $_FirstDisk.name) -TestResult $_filesystem_hana.StripeSize -ExptectedResult $_HANAStripeSize -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { # Wrong Disk Type AddCheckResultEntry -CheckID "HDB-FS-0004" -Description "SAP HANA Data: stripe size" -AdditionalInfo ("Disk " + $_FirstDisk.name) -TestResult $_filesystem_hana.StripeSize -ExptectedResult $_HANAStripeSize -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } } # if LVM is used check for same disk type as multiple volumes might be used foreach ($_AzureDisk_hana in $_AzureDisks_hana) { if ($_filesystem_hana_type -eq "lvm") { if ($_AzureDisk_hana.Disktype -eq $_FirstDisk.Disktype) { # disk type correct AddCheckResultEntry -CheckID "HDB-FS-0005" -Description "SAP HANA Data: same disk type" -AdditionalInfo ("Disk " + $_AzureDisk_hana.name) -TestResult $_AzureDisk_hana.Disktype -ExptectedResult $_FirstDisk.Disktype -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { # Wrong Disk Type AddCheckResultEntry -CheckID "HDB-FS-0005" -Description "SAP HANA Data: same disk type" -AdditionalInfo ("Disk " + $_AzureDisk_hana.name) -TestResult $_AzureDisk_hana.Disktype -ExptectedResult $_FirstDisk.Disktype -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } if ($_AzureDisk_hana.PERFORMANCETIER -eq $_FirstDisk.PERFORMANCETIER) { # disk type correct AddCheckResultEntry -CheckID "HDB-FS-0006" -Description "SAP HANA Data: same disk performance type" -AdditionalInfo ("Disk " + $_AzureDisk_hana.name) -TestResult $_AzureDisk_hana.Disktype -ExptectedResult $_FirstDisk.Disktype -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { # Wrong Disk Type AddCheckResultEntry -CheckID "HDB-FS-0006" -Description "SAP HANA Data: same disk performance type" -AdditionalInfo ("Disk " + $_AzureDisk_hana.name) -TestResult $_AzureDisk_hana.Disktype -ExptectedResult $_FirstDisk.Disktype -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } } # setting storage type for later checks $script:_StorageType += $_AzureDisk_hana.StorageType # check if storage type is supported if ($_jsonconfig.SupportedVMs.$_VMType.$VMRole.HANAStorageTypeData -contains $_AzureDisk_hana.StorageType) { # storage type is supported for HANA AddCheckResultEntry -CheckID "HDB-FS-0015" -Description "SAP HANA Data: storage type supported" -AdditionalInfo ("Storage Type " + $_AzureDisk_hana.StorageType) -TestResult "Supported" -ExptectedResult "Supported" -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { # Wrong Disk Type AddCheckResultEntry -CheckID "HDB-FS-0015" -Description "SAP HANA Data: storage type supported" -AdditionalInfo ("Storage Type " + $_AzureDisk_hana.StorageType) -TestResult "Unsupported" -ExptectedResult "Supported" -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } # check Premium SSD v2 sector size if ($_AzureDisk_hana.StorageType -eq "PremiumV2_LRS") { $_diskdevicename_command = "/sys/block/" + $_AzureDisk_hana.DeviceName.Split("/")[2] + "/queue/logical_block_size" $_sectorsize_command = PrepareCommand -Command "cat $_diskdevicename_command" -RootRequired $true -CommandType "OS" $_sectorsize = RunCommand -p $_sectorsize_command if ($_sectorsize -eq "4096") { # sector size supported AddCheckResultEntry -CheckID "HDB-FS-0017" -Description "SAP HANA Data: sector size Premium SSD V2" -AdditionalInfo "Sector size of 4096 bytes" -TestResult "4096" -ExptectedResult "4096" -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { # sector size unsupported AddCheckResultEntry -CheckID "HDB-FS-0017" -Description "SAP HANA Data: sector size Premium SSD V2" -AdditionalInfo "Sector size of 512 bytes" -TestResult "512" -ExptectedResult "4096" -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } } } } elseif (($_filesystem_hana.fstype -eq 'nfs') -or ($_filesystem_hana.fstype -eq 'nfs4')) { $script:_StorageType += "ANF" ## /hana/data is on NFS } else { ## file system not found } ## getting file system for /hana/log $_filesystem_hana = ($Script:_filesystems | Where-Object {$_.Target -in $script:DBLogDir}) if ([String]::IsNullOrEmpty($_filesystem_hana)){ # didn't find file system WriteRunLog -category "ERROR" -message "Couldn't find mounted file system $script:DBLogDir" if (-not $RunLocally) { exit } } if ($_filesystem_hana.Source.StartsWith("/dev/sd") -or $_filesystem_hana.Source.StartsWith("/dev/nvme0n")) { $_filesystem_hana_type = "direct" } else { $_filesystem_hana_type = "lvm" } if ( ($_filesystem_hana.fstype | Select-Object -Unique) -in @('xfs','nfs','nfs4')) { AddCheckResultEntry -CheckID "HDB-FS-0007" -Description "SAP HANA Log: File System" -TestResult $_filesystem_hana.fstype -ExptectedResult "xfs, nfs or nfs4" -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { AddCheckResultEntry -CheckID "HDB-FS-0007" -Description "SAP HANA Log: File System" -TestResult $_filesystem_hana.fstype -ExptectedResult "xfs, nfs or nfs4" -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } if ( (($script:_filesystems | Where-Object {$_.target -in $script:DBLogDir}).MaxMBPS | Measure-Object -Sum).Sum -ge $_jsonconfig.HANAStorageRequirements.HANALogMBPS) { AddCheckResultEntry -CheckID "HDB-FS-0008" -Description "SAP HANA Log: Disk Performance" -TestResult ($script:_filesystems | Where-Object {$_.target -eq $script:DBLogDir}).MaxMBPS -ExptectedResult ">= 250 MByte/s" -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { AddCheckResultEntry -CheckID "HDB-FS-0008" -Description "SAP HANA Log: Disk Performance" -TestResult ($script:_filesystems | Where-Object {$_.target -eq $script:DBLogDir}).MaxMBPS -ExptectedResult ">= 250 MByte/s" -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } if ($_filesystem_hana.fstype -eq 'xfs') { ## getting disks for /hana/log if ($_filesystem_hana_type -eq "lvm") { $_AzureDisks_hana = ($_AzureDisks | Where-Object {$_.VolumeGroup -in $_filesystem_hana.vg}) } else { $_AzureDisks_for_hanalog_filesystems = $script:_filesystems | Where-Object {$_.target -in $script:DBLogDir} # $_AzureDisks_hana = ($_AzureDisks | Where-Object { $_.DeviceName -in $_AzureDisks_for_hanalog_filesystems.Source}) $_AzureDisks_hana = ($_AzureDisks | Where-Object { $_AzureDisks_for_hanalog_filesystems.Source.Contains($_.DeviceName) }) } $_FirstDisk = $_AzureDisks_hana[0] # checking if IOPS need to be checked (Ultra Disk) if ($_FirstDisk.StorageType -eq "UltraSSD_LRS") { if ($_filesystem_hana.fstype -in @('xfs')) { if ( (($script:_filesystems | Where-Object {$_.target -in $script:DBLogDir}).MaxIOPS | Measure-Object -Sum).Sum -ge $_jsonconfig.HANAStorageRequirements.HANALogIOPS) { AddCheckResultEntry -CheckID "HDB-FS-0009" -Description "SAP HANA Log: Disk Performance" -TestResult ($script:_filesystems | Where-Object {$_.target -eq $script:DBLogDir}).MaxIOPS -ExptectedResult ">= 2000 IOPS" -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { AddCheckResultEntry -CheckID "HDB-FS-0009" -Description "SAP HANA Log: Disk Performance" -TestResult ($script:_filesystems | Where-Object {$_.target -eq $script:DBLogDir}).MaxIOPS -ExptectedResult ">= 2000 IOPS" -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } } } # check if stripe size check required (no of disks greater than 1 in VG) if (($_AzureDisks_hana.count -gt 1) -and ($_filesystem_hana_type -eq "lvm")) { $_HANAStripeSize = $_jsonconfig.HANAStorageRequirements.HANALogStripeSize if ($_filesystem_hana.StripeSize -eq $_HANAStripeSize) { # stripe size correct AddCheckResultEntry -CheckID "HDB-FS-0010" -Description "SAP HANA Log: stripe size" -AdditionalInfo ("Disk " + $_FirstDisk.name) -TestResult $_filesystem_hana.StripeSize -ExptectedResult $_HANAStripeSize -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { # Wrong Disk Type AddCheckResultEntry -CheckID "HDB-FS-0010" -Description "SAP HANA Log: stripe size" -AdditionalInfo ("Disk " + $_FirstDisk.name) -TestResult $_filesystem_hana.StripeSize -ExptectedResult $_HANAStripeSize -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } } foreach ($_AzureDisk_hana in $_AzureDisks_hana) { if ($_AzureDisk_hana.Disktype -eq $_FirstDisk.Disktype) { # disk type correct AddCheckResultEntry -CheckID "HDB-FS-0011" -Description "SAP HANA Log: same disk type" -AdditionalInfo ("Disk " + $_AzureDisk_hana.name) -TestResult $_AzureDisk_hana.Disktype -ExptectedResult $_FirstDisk.Disktype -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { # Wrong Disk Type AddCheckResultEntry -CheckID "HDB-FS-0011" -Description "SAP HANA Log: same disk type" -AdditionalInfo ("Disk " + $_AzureDisk_hana.name) -TestResult $_AzureDisk_hana.Disktype -ExptectedResult $_FirstDisk.Disktype -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } if ($_AzureDisk_hana.PERFORMANCETIER -eq $_FirstDisk.PERFORMANCETIER) { # disk type correct AddCheckResultEntry -CheckID "HDB-FS-0012" -Description "SAP HANA Log: same disk performance type" -AdditionalInfo ("Disk " + $_AzureDisk_hana.name) -TestResult $_AzureDisk_hana.PERFORMANCETIER -ExptectedResult $_FirstDisk.PERFORMANCETIER -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { # Wrong Disk Type AddCheckResultEntry -CheckID "HDB-FS-0012" -Description "SAP HANA Log: same disk performance type" -AdditionalInfo ("Disk " + $_AzureDisk_hana.name) -TestResult $_AzureDisk_hana.PERFORMANCETIER -ExptectedResult $_FirstDisk.PERFORMANCETIER -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } if ($_AzureDisk_hana.StorageType -eq "Premium_LRS") { # Premium Disk - Check for Write Accelerator if ($_AzureDisk_hana.WriteAccelerator -eq "true") { AddCheckResultEntry -CheckID "HDB-FS-0013" -Description "SAP HANA Log: Write Accelerator enabled" -AdditionalInfo ("Disk " + $_AzureDisk_hana.name) -TestResult $_AzureDisk_hana.WriteAccelerator -ExptectedResult "true" -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { AddCheckResultEntry -CheckID "HDB-FS-0013" -Description "SAP HANA Log: Write Accelerator enabled" -AdditionalInfo ("Disk " + $_AzureDisk_hana.name) -TestResult $_AzureDisk_hana.WriteAccelerator -ExptectedResult "true" -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } } # setting storage type for later checks $script:_StorageType += $_AzureDisk_hana.StorageType # check if storage type is supported if ($_jsonconfig.SupportedVMs.$_VMType.$VMRole.HANAStorageTypeLog -contains $_AzureDisk_hana.StorageType) { # storage type is supported for HANA AddCheckResultEntry -CheckID "HDB-FS-0016" -Description "SAP HANA Log: storage type supported" -AdditionalInfo ("Storage Type " + $_AzureDisk_hana.StorageType) -TestResult "Supported" -ExptectedResult "Supported" -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { # Wrong Disk Type AddCheckResultEntry -CheckID "HDB-FS-0016" -Description "SAP HANA Log: storage type supported" -AdditionalInfo ("Storage Type " + $_AzureDisk_hana.StorageType) -TestResult "Unsupported" -ExptectedResult "Supported" -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } # check Premium SSD v2 sector size if ($_AzureDisk_hana.StorageType -eq "PremiumV2_LRS") { $_diskdevicename_command = "/sys/block/" + $_AzureDisk_hana.DeviceName.Split("/")[2] + "/queue/logical_block_size" $_sectorsize_command = PrepareCommand -Command "cat $_diskdevicename_command" -RootRequired $true -CommandType "OS" $_sectorsize = RunCommand -p $_sectorsize_command if ($_sectorsize -eq "4096") { # sector size supported AddCheckResultEntry -CheckID "HDB-FS-0018" -Description "SAP HANA Log: sector size Premium SSD V2" -AdditionalInfo "Sector size of 4096 bytes" -TestResult "4096" -ExptectedResult "4096" -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { # sector size unsupported AddCheckResultEntry -CheckID "HDB-FS-0018" -Description "SAP HANA Log: sector size Premium SSD V2" -AdditionalInfo "Sector size of 512 bytes" -TestResult "512" -ExptectedResult "4096" -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } } } } elseif (($_filesystem_hana.fstype -eq 'nfs') -or ($_filesystem_hana.fstype -eq 'nfs4')) { $script:_StorageType += "ANF" ## /hana/data is on NFS } else { ## file system not found } # check /hana/shared directory $_filesystem_hana = $_filesystems | Where-Object {$_.target -eq $script:DBSharedDir} if ($_filesystem_hana.fstype -in @('xfs','nfs','nfs4')) { AddCheckResultEntry -CheckID "HDB-FS-0014" -Description "SAP HANA Shared: File System" -TestResult $_filesystem_hana.fstype -ExptectedResult "xfs, nfs or nfs4" -Status "OK" -MicrosoftDocs $_saphanastorageurl } else { AddCheckResultEntry -CheckID "HDB-FS-0014" -Description "SAP HANA Shared: File System" -TestResult $_filesystem_hana.fstype -ExptectedResult "xfs, nfs or nfs4" -Status "ERROR" -MicrosoftDocs $_saphanastorageurl -ErrorCategory "ERROR" } } # STORAGE CHECKS IBM DB2 # checking for data disks if (($VMDatabase -eq "Db2") -and ($VMRole -eq "DB")) { $_db2storagedocsurl = "https://learn.microsoft.com/en-us/azure/sap/workloads/dbms-guide-ibm" if ($script:DBDataDir.Contains("/hana/data")) { #default value is being used for DBDataDir #Rewrite the correct default values for DB2 if ($SID) { $script:_persistance_db2datavolumes = "/db2/" + $SID + "/sapdata" $script:_persistance_db2logvolumes = "/db2/" + $SID + "/log_dir" # get all files for /db2/SID/sapdata $_commandstring = "find $_persistance_db2datavolumes -type f" $_command = PrepareCommand -Command $($_commandstring) -CommandType "OS" -RootRequired $true $script:_persistance_datavolumes_files = RunCommand -p $_command # get all files for /db2/SID/log_dir $_commandstring = "find $_persistance_db2logvolumes -type f" $_command = PrepareCommand -Command $($_commandstring) -CommandType "OS" -RootRequired $true $script:_persistance_logvolumes_files = RunCommand -p $_command # loop through all files and get the file systems they are using $_datavolumes_filesystems = @() foreach ($_datavolumes_file in $script:_persistance_datavolumes_files) { $_command = PrepareCommand -Command "findmnt -T $_datavolumes_file | tail -n +2" -CommandType "OS" -RootRequired $true $_findmnt_temp = RunCommand -p $_command $_findmnt_temp = ConvertFrom-String_findmnt -p $_findmnt_temp $_datavolumes_filesystems += $_findmnt_temp.target } # loop through all files and get the file systems they are using $_logvolumes_filesystems = @() foreach ($_logvolumes_file in $script:_persistance_logvolumes_files) { $_command = PrepareCommand -Command "findmnt -T $_logvolumes_file | tail -n +2" -CommandType "OS" -RootRequired $true $_findmnt_temp = RunCommand -p $_command $_findmnt_temp = ConvertFrom-String_findmnt -p $_findmnt_temp $_logvolumes_filesystems += $_findmnt_temp.target } $script:DBDataDir = $_datavolumes_filesystems $script:DBLogDir = $_logvolumes_filesystems } else { WriteRunLog -message "SID is required parameter for running DB2 checks." -category "ERROR" } } ## getting file system for /db2/SID/log_dir $_filesystem_db2 = ($script:_filesystems | Where-Object {$_.Target -in $script:DBLogDir}) if ([String]::IsNullOrEmpty($_filesystem_db2)){ # didn't find file system WriteRunLog -category "ERROR" -message "Couldn't find mounted file system $script:DBLogDir" if (-not $RunLocally) { exit } } if ($_filesystem_db2.Source.StartsWith("/dev/sd") -or $_filesystem_db2.Source.StartsWith("/dev/nvme0n")) { $_filesystem_db2_type = "direct" } else { $_filesystem_db2_type = "lvm" } if($_filesystem_db2.fstype -eq 'xfs') { if ($_filesystem_db2_type -eq "lvm") { $_AzureDisks_db2 = ($_AzureDisks | Where-Object {$_.VolumeGroup -in $_filesystem_db2.vg}) } else { $_AzureDisks_for_db2data_filesystems = $script:_filesystems | Where-Object {$_.target -in $script:DBLogDir} $_AzureDisks_db2 = ($_AzureDisks | Where-Object { $_.DeviceName -in $_AzureDisks_for_db2data_filesystems.Source}) } } elseif (($_filesystem_db2.fstype -eq 'nfs') -or ($_filesystem_db2.fstype -eq 'nfs4')) { $script:_StorageType += "ANF" } else { ## file system not found } # check if stripe size check required (no of disks greater than 1 in VG) if (($_AzureDisks_db2.count -gt 1) -and ($_filesystem_db2_type -eq "lvm")) { $_DB2StripeSize = $_jsonconfig.DB2StorageRequirements.DB2LogStripeSize if ($_filesystem_db2.StripeSize -eq $_DB2StripeSize) { # stripe size correct AddCheckResultEntry -CheckID "DB2-RHEL-0001" -Description "IBM DB2 Log: stripe size" -TestResult $_filesystem_db2.StripeSize -ExptectedResult $_DB2StripeSize -Status "OK" -MicrosoftDocs $_db2storagedocsurl } else { # Wrong Disk Type AddCheckResultEntry -CheckID "DB2-RHEL-0001" -Description "IBM DB2 Log: stripe size" -TestResult $_filesystem_db2.StripeSize -ExptectedResult $_DB2StripeSize -Status "ERROR" -MicrosoftDocs $_db2storagedocsurl -ErrorCategory "ERROR" } } ## getting file system for /db2/data $_filesystem_db2 = ($script:_filesystems | Where-Object {$_.Target -in $script:DBDataDir}) if ([String]::IsNullOrEmpty($_filesystem_db2)){ # didn't find file system WriteRunLog -category "ERROR" -message "Couldn't find mounted file system $script:DBDataDir" if (-not $RunLocally) { exit } } if ($_filesystem_db2.Source.StartsWith("/dev/sd") -or $_filesystem_db2.Source.StartsWith("/dev/nvme0n")) { $_filesystem_db2_type = "direct" } else { $_filesystem_db2_type = "lvm" } if($_filesystem_db2.fstype -eq 'xfs') { if ($_filesystem_db2_type -eq "lvm") { $_AzureDisks_db2 = ($_AzureDisks | Where-Object {$_.VolumeGroup -in $_filesystem_db2.vg}) } else { $_AzureDisks_for_db2data_filesystems = $script:_filesystems | Where-Object {$_.target -in $script:DBDataDir} $_AzureDisks_db2 = ($_AzureDisks | Where-Object { $_.DeviceName -in $_AzureDisks_for_db2data_filesystems.Source}) } } elseif (($_filesystem_db2.fstype -eq 'nfs') -or ($_filesystem_db2.fstype -eq 'nfs4')) { $script:_StorageType += "ANF" } else { ## file system not found } # check if stripe size check required (no of disks greater than 1 in VG) if (($_AzureDisks_db2.count -gt 1) -and ($_filesystem_db2_type -eq "lvm")) { $_DB2StripeSize = $_jsonconfig.DB2StorageRequirements.DB2DataStripeSize if ($_filesystem_db2.StripeSize -eq $_DB2StripeSize) { # stripe size correct AddCheckResultEntry -CheckID "DB2-RHEL-0002" -Description "IBM DB2 Data: stripe size" -TestResult $_filesystem_db2.StripeSize -ExptectedResult $_DB2StripeSize -Status "OK" -MicrosoftDocs $_db2storagedocsurl } else { AddCheckResultEntry -CheckID "DB2-RHEL-0002" -Description "IBM DB2 Data: stripe size" -TestResult $_filesystem_db2.StripeSize -ExptectedResult $_DB2StripeSize -Status "ERROR" -MicrosoftDocs $_db2storagedocsurl -ErrorCategory "ERROR" } } } # remove duplicates from used storage types $script:_StorageType = $script:_StorageType | Select-Object -Unique if (($VMDatabase -eq "HANA") -and ($script:_StorageType.Length -lt 1)) { WriteRunLog -category "ERROR" -message "please check your parameters, HANA directories not found" $script:_RunqualityCheckResult = $false return $false } # run checks from JSON file foreach ($_check in $_jsonconfig.Checks) { # does the check apply to this system? if ( $_check.OS.Contains($VMOperatingSystem) -and ` $_check.DB.Contains($VMDatabase) -and ` $_check.Role.Contains($VMRole) -and ` ( $_check.OSVersion.Contains("all") -or $_check.OSVersion.Contains($VMOSRelease)) -and ` (((Compare-Object -ReferenceObject $_check.StorageType -DifferenceObject $script:_StorageType -IncludeEqual -ExcludeDifferent) | Measure-Object ).count -gt 0) -and ` $_check.Hardwaretype.Contains($Hardwaretype)) { # check if check applies to HA or not and if HA check for HA-Agent # if (($_check.HighAvailability.Contains($false)) -or (($_check.HighAvailability.Contains($HighAvailability)) -and ($_check.HighAvailabilityAgent.Contains($HighAvailabilityAgent)))) { if (($HighAvailability -eq $false) -and ($_check.HighAvailability.Contains($HighAvailability)) -or ` (($HighAvailability -eq $true) -and ($_check.HighAvailability.Contains($HighAvailability)) -and ($_check.HighAvailabilityAgent.Contains($HighAvailabilityAgent)))) { if (($_check.Feature -and $script:_InstalledFeatures.Contains($_check.Feature)) -or (-not $_check.Feature)) { if ((-not $RunLocally) -or ($RunLocally -and ($_check.RunInLocalMode))) { if (-not $RunLocally) { $_Check_row = "" | Select-Object CheckID, Description, AdditionalInfo, Testresult, ExpectedResult, Status, SAPNote, MicrosoftDocs } else { $_Check_row = "" | Select-Object CheckID, Description, AdditionalInfo, Testresult, ExpectedResult, Status, SAPNote, MicrosoftDocs, Success, VmRole } $_result = RunCommand -p $_check $_result = RemoveTabsAndSpaces -OriginalString $_result $_Check_row.CheckID = $_check.CheckID $_Check_row.Description = $_check.Description $_Check_row.AdditionalInfo = $_check.AdditionalInfo $_Check_row.Testresult = $_result # $_Check_row.ExpectedResult = $_check.ExpectedResult # if multiple expected results are available the join will combine them and add a new line for each entry if ($_check.ExpectedResult.GetType().Name -eq "PSCustomObject") { switch ($_check.ExpectedResult.Type) { "multi" { $_Check_row.ExpectedResult = $_check.ExpectedResult.Values -join (" or{0}" -f [environment]::NewLine) } "range" { $_Check_row.ExpectedResult = "from {0} to {1}" -f $_check.ExpectedResult.low, $_check.ExpectedResult.high } "dynamic" { $_dynamicresult = Invoke-Expression $_check.ExpectedResult $_Check_row.ExpectedResult = $_dynamicresult } "dynamicmulti" { $_multiresult = @() foreach ($_value in $_check.ExpectedResult.Values) { $_multiresult += Invoke-Expression $_value } $_Check_row.ExpectedResult = $_multiresult -join (" or{0}" -f [environment]::NewLine) } "dynamicrange" { $_calculated_low = Invoke-Expression $_check.function.low $_calculated_high = Invoke-Expression $_check.function.high $_Check_row.ExpectedResult = "from {0} to {1}" -f $_calculated_low, $_calculated_high } Default { "wrong default value in JSON"} } } else { $_Check_row.ExpectedResult = $_check.ExpectedResult } if ($RunLocally) { $_Check_row.VmRole = $VMRole } # if ($_check.SAPNote -ne "") { if (![string]::IsNullOrEmpty($_check.SAPNote)) { $_SAPNotes = @() foreach ($_SAPNote in $_check.SAPNote) { if (-not $RunLocally) { # $_Check_row.SAPNote = "::SAPNOTEHTML1::" + $_check.SAPNote + "::SAPNOTEHTML2::" + $_check.SAPNote + "::SAPNOTEHTML3::" $_SAPNotes += "::SAPNOTEHTML1::" + $_SAPNote + "::SAPNOTEHTML2::" + $_SAPNote + "::SAPNOTEHTML3::" } else { # $_Check_row.SAPNote = $_check.SAPNote $_SAPNotes += $_SAPNote } } $_Check_row.SAPNote = $_SAPNotes -join ("{0}" -f [environment]::NewLine) } # if ($_check.MicrosoftDocs -ne "") { if (![string]::IsNullOrEmpty($_check.MicrosoftDocs)) { $_HTMLLinks = @() foreach ($_HTMLLink in $_check.MicrosoftDocs) { if (-not $RunLocally) { # $_Check_row.MicrosoftDocs = "::MSFTDOCS1::" + $_check.MicrosoftDocs + "::MSFTDOCS2::" + "Link" + "::MSFTDOCS3::" $_HTMLLinks += "::MSFTDOCS1::" + $_HTMLLink + "::MSFTDOCS2::" + "Link" + "::MSFTDOCS3::" } else { # $_HTMLLinks += $_check.MicrosoftDocs $_HTMLLinks += $_HTMLLink } } $_Check_row.MicrosoftDocs = $_HTMLLinks -join ("{0}" -f [environment]::NewLine) } # check if the expected result has multiple values or just one # if ($_check.ExpectedResult.GetType().Name -eq "Object[]") { if ($_check.ExpectedResult.GetType().Name -eq "PSCustomObject") { switch ($_check.ExpectedResult.type) { "multi" { if ($_check.ExpectedResult.Values -contains $_result) { $_Check_row.Status = "OK" if ($RunLocally) { $_Check_row.Success = $true } } else { # $_Check_row.Status = "ERROR" $_Check_row.Status = $_check.ErrorCategory if ($RunLocally) { $_Check_row.Success = $false } } } "range" { if ([int]$_result -ge [int]$_check.ExpectedResult.low -and [int]$_result -le [int]$_check.ExpectedResult.high) { $_Check_row.Status = "OK" if ($RunLocally) { $_Check_row.Success = $true } } else { # $_Check_row.Status = "ERROR" $_Check_row.Status = $_check.ErrorCategory if ($RunLocally) { $_Check_row.Success = $false } } } "dynamic" { $_dynamicresult = Invoke-Expression $_check.ExpectedResult if ($_result -eq $_dynamicresult) { $_Check_row.Status = "OK" if ($RunLocally) { $_Check_row.Success = $true } } else { # $_Check_row.Status = "ERROR" $_Check_row.Status = $_check.ErrorCategory if ($RunLocally) { $_Check_row.Success = $false } } } "dynamicmulti" { $_multiresult = @() foreach ($_value in $_check.ExpectedResult.Values) { $_multiresult += Invoke-Expression $_value } if ($_multiresult -contains $_result) { $_Check_row.Status = "OK" if ($RunLocally) { $_Check_row.Success = $true } } else { # $_Check_row.Status = "ERROR" $_Check_row.Status = $_check.ErrorCategory if ($RunLocally) { $_Check_row.Success = $false } } } "dynamicrange" { $_calculated_low = Invoke-Expression $_check.function.low $_calculated_high = Invoke-Expression $_check.function.high if ([int]$_result -ge [int]$_calculated_low -and [int]$_result -le [int]$_calculated_high) { $_Check_row.Status = "OK" if ($RunLocally) { $_Check_row.Success = $true } } else { # $_Check_row.Status = "ERROR" $_Check_row.Status = $_check.ErrorCategory if ($RunLocally) { $_Check_row.Success = $false } } } Default { $_Check_row.Status = "JSONERROR" } } } else { if ($_result -eq $_check.ExpectedResult) { $_Check_row.Status = "OK" if ($RunLocally) { $_Check_row.Success = $true } } else { # $_Check_row.Status = "ERROR" $_Check_row.Status = $_check.ErrorCategory if ($RunLocally) { $_Check_row.Success = $false } } } if (($_check.ShowAlternativeRequirement) -ne "" -or ($_check.ShowAlternativeResult -ne "")) { if (-not [string]::IsNullOrEmpty($_check.ProcessingCommandOutput)) { $_checkoutputcommand = PrepareCommand -Command $_check.ProcessingCommandOutput -CommandType $_check.CommandType $_resultoutputcommand = RunCommand -p $_checkoutputcommand $_Check_row.Testresult = $_resultoutputcommand -join ("{0}" -f [environment]::NewLine) } else { if ($_check.ShowAlternativeResult -ne "") { $_Check_row.Testresult = Invoke-Expression $_check.ShowAlternativeResult } else { $_Check_row.Testresult = "" } } if (![string]::IsNullOrEmpty($_check.ShowAlternativeRequirement)) { $_Check_row.ExpectedResult = Invoke-Expression $_check.ShowAlternativeRequirement } else { $_Check_row.ExpectedResult = "" } } $script:_Checks += $_Check_row } } } } } $_ChecksOutput = $script:_Checks | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""Checks"">Check Results</h2>" # replace the placeholders for HTML links with HTML content ## SAP Part $_ChecksOutput = $_ChecksOutput -replace '<td>OK</td>','<td class="StatusOK">OK</td>' $_ChecksOutput = $_ChecksOutput -replace '<td>ERROR</td>','<td class="StatusError">ERROR</td>' $_ChecksOutput = $_ChecksOutput -replace '<td>WARNING</td>','<td class="StatusWarning">WARNING</td>' $_ChecksOutput = $_ChecksOutput -replace '<td>INFO</td>','<td class="StatusInfo">INFO</td>' # $_ChecksOutput = $_ChecksOutput -replace '::SAPNOTEHTML1::','<a href="https://launchpad.support.sap.com/#/notes/' $_ChecksOutput = $_ChecksOutput -replace '::SAPNOTEHTML1::','<a href="https://me.sap.com/notes/' $_ChecksOutput = $_ChecksOutput -replace '::SAPNOTEHTML2::','" target="_blank">' $_ChecksOutput = $_ChecksOutput -replace '::SAPNOTEHTML3::','</a>' ## MSFT part $_ChecksOutput = $_ChecksOutput -replace '::MSFTDOCS1::','<a href="' $_ChecksOutput = $_ChecksOutput -replace '::MSFTDOCS2::','" target="_blank">' $_ChecksOutput = $_ChecksOutput -replace '::MSFTDOCS3::','</a>' $script:_Content += "<a href=""#Checks"">Check Results</a><br>" return $_ChecksOutput } # collect file system infos function CollectFileSystems { if ($VMOperatingSystem -eq "Windows") { # Get information about Storage pool $script:_datadisks = Invoke-Command -ComputerName $AzVMName -ScriptBlock {Get-StoragePool} # create HTML output $_FilesystemsOutput = $script:_datadisks | select-object FriendlyName, OperationalStatus, HealthStatus, IsPrimordial, IsReadOnly, @{Name="Size (GB)";Expression={[math]::Round($_.Size/1GB,2)}},AllocatedSize | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""Storage Pool"">Storage Pool</h2>This section shows you the Storage Pool configured on the windows VM." # add entry in HTML index $script:_Content += "<a href=""#Storage Pool"">Storage Pool</a><br>" return $_FilesystemsOutput } else { # linux systems # run findmnt command on OS $_command = PrepareCommand -Command "findmnt -r -n -it autofs" -CommandType "OS" $script:_findmnt = RunCommand -p $_command $script:_findmnt = ConvertFrom-String_findmnt -p $script:_findmnt # run df command on OS $_command = PrepareCommand -Command "df -BG" -CommandType "OS" $script:_filesystemfree = RunCommand -p $_command $script:_filesystemfree = ConvertFrom-String_df -p $script:_filesystemfree # new empty object for file systems $script:_filesystems = @() # loop through df command output foreach ($_filesystem in $_filesystemfree) { # new empty object for file system entry $_filesystem_row = "" | Select-Object Target,Source,FSType,VG,Size,Free,Used,UsedPercent,MaxMBPS,MaxIOPS,StripeSize,Options $_filesystem_row.Target = $_filesystem.Mountpoint $_filesystem_row.Source = $_filesystem.Filesystem $_filesystem_row.FSType = ($script:_findmnt | Where-Object {$_.target -eq $_filesystem.Mountpoint}).fstype $_filesystem_row.Options = ($script:_findmnt | Where-Object {$_.target -eq $_filesystem.Mountpoint}).options $_filesystem_row.Size = $_filesystem.Size $_filesystem_row.Free = $_filesystem.Free $_filesystem_row.Used = $_filesystem.Used $_filesystem_row.UsedPercent = $_filesystem.UsedPercent $_filesystem_row.VG = ($script:_lvmvolumes | Where-Object { $_.dmpath -eq $_filesystem.Filesystem}).vgname $_filesystem_row.StripeSize = ($script:_lvmvolumes | Where-Object { $_.dmpath -eq $_filesystem.Filesystem}).stripesize if (($_filesystem_row.FSType -eq "nfs") -or ($_filesystem_row.FSType -eq "nfs4")) { # NEW NFS volume selection $_nfsaddress = ($_filesystem_row.Source.Split(":"))[0] if ([bool]$_nfsaddress -as [ipaddress]) { # NFS hostname is an ipaddress $_nfsipaddress = $_nfsaddress WriteRunLog -message "Found NFS share $($_filesystem_row.Source) with IP address $_nfsipaddress" $_nfsisdnsname = $true } else { # NFS hostname is a DNS name, need ip address $_nfsisdnsname = $false WriteRunLog -message "Found NFS share $($_filesystem_row.Source) with DNS name, getting IP address" # $_command = PrepareCommand -Command "ping -q -c 1 -t 1 -W 1 $_nfsaddress | grep -m 1 PING | cut -d '(' -f2 | cut -d ')' -f1" -CommandType "OS" $_command = PrepareCommand -Command "getent hosts $_nfsaddress | cut -d ' ' -f1" -CommandType "OS" $_nfsipaddress = RunCommand -p $_command WriteRunLog -message "Found NFS share $($_filesystem_row.Source) with IP address $_nfsipaddress" } $_nfssearch1=$_filesystem_row.Source $_nfssearch2=$_nfsipaddress + ":" + ($_filesystem_row.Source.Split(":"))[1] if ($_nfsipaddress.contains("privatelink")) { $_nfsprivatelink = $true } else { $_nfsprivatelink = $false } if ($_nfsipaddress.contains("file.core.windows.net")) { $_nfsazurefilesdns = $true } else { $_nfsazurefilesdns = $false } try { $_filesystem_row.MaxMBPS = ($script:_nfsvolumes | Where-Object { $_nfssearch2.Startswith($_.NFSAddress) -or $_nfssearch1.Startswith($_.NFSAddressDNS)}).THROUGHPUTMIBPS $_filesystem_row.MaxIOPS = ($script:_nfsvolumes | Where-Object { $_nfssearch2.Startswith($_.NFSAddress) -or $_nfssearch1.Startswith($_.NFSAddressDNS)}).IOPS } catch { # problem getting NFS volume WriteRunLog -message "Problem with getting Throughput and/or IOPS for NFS volume" -category WARNING } # NFS ANF volumes need throughput values from ANF infos #$_filesystem_row.MaxMBPS = ($script:_ANFVolumes | Where-Object { $_filesystem_row.Source.Equals($_.NFSAddress) }).THROUGHPUTMIBPS #if ((($script:_ANFVolumes | Where-Object { $_filesystem_row.Source.Equals($_.NFSAddress) }).THROUGHPUTMIBPS).count -eq 0) { # # backup path for ANF volumes to have DNS names covered # # this path will just compared the volume export name # $_NFSmounttemp = ($_filesystem_row.Source.Split(":"))[1] # if (![string]::IsNullOrEmpty($_NFSmounttemp)) { # $_filesystem_row.MaxMBPS = ($script:_ANFVolumes | Where-Object { $_NFSmounttemp.Equals(($_.NFSAddress.Split(":"))[1]) }).THROUGHPUTMIBPS # if ([string]::IsNullOrEmpty($_filesystem_row.MaxMBPS)) { # $_filesystem_row.MaxMBPS = ($script:_ANFVolumes | Where-Object { $_NFSmounttemp.StartsWith(($_.NFSAddress.Split(":"))[1]) }).THROUGHPUTMIBPS # if ([string]::IsNullOrEmpty($_filesystem_row.MaxMBPS)) { # $_filesystem_row.MaxMBPS = ($script:_ANFVolumes | Where-Object { $_filesystem_row.Source.StartsWith($_.NFSAddress) }).THROUGHPUTMIBPS # } # } # } #} } else { if ($_filesystem.Filesystem.StartsWith("/dev/sd") -or $_filesystem.Filesystem.StartsWith("/dev/nvme0n")) { # add IOPS and MBPS from disk infos # $_filesystem_row.MaxMBPS = ($script:_AzureDisks | Where-Object { $_.DeviceName -eq $_filesystem_row.Source}).MBPS # $_filesystem_row.MaxIOPS = ($script:_AzureDisks | Where-Object { $_.DeviceName -eq $_filesystem_row.Source}).IOPS $_filesystem_row.MaxMBPS = ($script:_AzureDisks | Where-Object { $_filesystem_row.Source.Contains($_.DeviceName)}).MBPS $_filesystem_row.MaxIOPS = ($script:_AzureDisks | Where-Object { $_filesystem_row.Source.Contains($_.DeviceName)}).IOPS } else { # add IOPS and MBPS from LVM infos $_filesystem_row.MaxMBPS = ($script:_lvmgroups | Where-Object { $_.name -eq $_filesystem_row.VG}).TotalMBPS $_filesystem_row.MaxIOPS = ($script:_lvmgroups | Where-Object { $_.name -eq $_filesystem_row.VG}).TotalIOPS } } $script:_filesystems += $_filesystem_row } # create HTML output $_FilesystemsOutput = $script:_filesystems | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""Filesystems"">Filesystems</h2>This section shows you the file systems available on the VM." # add entry in HTML index $script:_Content += "<a href=""#Filesystems"">Filesystems</a><br>" return $_FilesystemsOutput } } # Windows Cluster Config Info function CollectWindowsClusterInfo { if ($VMOperatingSystem -eq "Windows") { # Display the cluster nodes on the system $_winclusternodes = Invoke-Command -ComputerName $AzVMName -ScriptBlock {Get-ClusterResource} # create HTML output $_winclusternodesOutput = $_winclusternodes |Select-Object Cluster, State, OwnerGroup, OwnerNode, ResourceType, MaintenanceMode| ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""Cluster Nodes"">Cluster Nodes</h2>This section shows you the Windows Cluster Nodes available on the VM." # add entry in HTML index $script:_Content += "<a href=""#Cluster Nodes"">Cluster Nodes</a><br>" return $_winclusternodesOutput } } # Windows Mini Filter drivers function MiniFilterWindowsInfo { if ($VMOperatingSystem -eq "Windows") { # Display the MiniFilter Drivers on the system $_winminifilters = Invoke-Command -ComputerName $AzVMName -ScriptBlock {fltmc} # create HTML output $_winminifiltersOutput =$_winminifilter | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""MiniFilter Drivers"">MiniFilter Drivers</h2>This section shows you the Windows MiniFilter attached to the Volumes on the VM." # add entry in HTML index $script:_Content += "<a href=""#Installed MiniFilter drivers"">MiniFilter Drivers</a><br>" return $_winminifiltersOutput } } # Windows HotFix Lists function MiniFilterWindowsInfo { if ($VMOperatingSystem -eq "Windows") { # Display the MiniFilter Drivers on the system $_winhotfix = Invoke-Command -ComputerName $AzVMName -ScriptBlock {Get-HotFix} # create HTML output $_winhotfixOutput =$_winhotfix | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""Windows HotFix"">Windows HotFix</h2>This section shows you the Windows HotFix Installed on the VM." # add entry in HTML index $script:_Content += "<a href=""#HotFixs"">Windows HotFix</a><br>" return $_winhotfixOutput } } function CollectNFSVolumes { $script:_nfsvolumes = @() # Get NFS data for ANF volumes $_anfaccounts = @() $_resourcegroups = Get-AzResourceGroup foreach ($_resourcegroup in $_resourcegroups) { $_anfaccount = Get-AzNetAppFilesAccount -ResourceGroupName $_resourcegroup.ResourceGroupName if ($_anfaccount) { $_anfaccounts += $_anfaccount foreach ($_anfaccount in $_anfaccounts) { $_anfpools = Get-AzNetAppFilesPool -ResourceGroupName $_anfaccount.ResourceGroupName -AccountName $_anfaccount.Name foreach ($_anfpool in $_anfpools) { $_anfpool_split = $_anfpool.Name.Split("/") $_anfpoolname = $_anfpool_split[1] $_anfvolumes = Get-AzNetAppFilesVolume -ResourceGroupName $_anfaccount.ResourceGroupName -AccountName $_anfaccount.Name -PoolName $_anfpoolname foreach ($_anfvolume in $_anfvolumes) { $_nfsvolume_row = "" | Select-Object Name,Pool,ServiceLevel,ThroughputMibps,ProtocolTypes,NFSAddress,NFSAddressDNS,QoSType,Type,IOPS,Id $_nfsvolume_row.Type="ANF" $_nfsvolume_row.Id = $_anfvolume.Id $_nfsvolume_row.Name = ($_anfvolume.Name -split '/')[2] $_nfsvolume_row.Pool = $_anfpoolname $_nfsvolume_row.ServiceLevel = $_anfvolume.ServiceLevel $_nfsvolume_row.ProtocolTypes = [string]$_anfvolume.ProtocolTypes # $_ANFVolume_row.ThroughputMibps = [int]$_ANFVolume.ThroughputMibps $_nfsvolume_row.ThroughputMibps = (([int]$_anfvolume.ThroughputMibps, [int]$_anfvolume.ActualThroughputMibps) | Measure-Object -Maximum).Maximum $_nfsvolume_row.QoSType = $_ANFPool.QosType if ($_anfvolume.MountTargets.Count -gt 0){ $_nfsvolume_row.NFSAddress = $_anfvolume.MountTargets[0].IpAddress + ":/" + $_anfvolume.CreationToken } $script:_nfsvolumes += $_nfsvolume_row } } } } } $_storageaccounts = Get-AzStorageAccount | Where-Object {$_.Kind -eq "FileStorage"} foreach ($_storageaccount in $_storageaccounts) { $_shares = Get-AzRmStorageShare -ResourceGroupName $_storageaccount.ResourceGroupName -StorageAccountName $_storageaccount.StorageAccountName foreach ($_share in $_shares) { if ($_share.EnabledProtocols -eq "NFS") { $_fileService = Get-AzStorageFileServiceProperty -ResourceGroupName $_storageaccount.ResourceGroupName -StorageAccountName $_storageaccount.StorageAccountName # Get the DNS name for the storage account $_dnsName = "$($storageAccount.StorageAccountName).file.core.windows.net" # Get Private Endpoints for storage account $_privateEndpoints = Get-AzPrivateEndpoint | Where-Object { $_.PrivateLinkServiceConnections.PrivateLinkServiceId -eq $_storageaccount.Id } # Get all IP addresses for the Private Endpoint $_privateEndpointsIPAddresses = $_privateEndpoints.CustomDnsConfigs.IpAddresses foreach ($_privateEndpointsIPAddress in $_privateEndpointsIPAddresses) { $_nfsvolume_row = "" | Select-Object Name,Pool,ServiceLevel,ThroughputMibps,ProtocolTypes,NFSAddress,NFSAddressDNS,QoSType,Type,IOPS,Id $_nfsvolume_row.Type = "AFS" $_nfsvolume_row.Name = $_share.Name $_nfsvolume_row.Pool = $_storageaccount.StorageAccountName $_nfsvolume_row.ProtocolTypes = "NFS4.1" $_nfsvolume_row.ServiceLevel = $_share.AccessTier $_nfsvolume_row.NFSAddressDNS = $_dnsName + ":/" + $_storageaccount.StorageAccountName + "/" + $_share.Name $_nfsvolume_row.NFSAddress = $_privateEndpointsIPAddress + ":/" + $_storageaccount.StorageAccountName + "/" + $_share.Name $_nfsvolume_row.ThroughputMibps = 100 + [Math]::Ceiling(0.04 * $_share.QuotaGiB) + [Math]::Ceiling(0.06 * $_share.QuotaGiB) $_nfsvolume_row.IOPS = [math]::Min(3000 + 1 * $_share.QuotaGiB, 100000) $_nfsvolume_row.Id = $_storageaccount.Id $script:_nfsvolumes += $_nfsvolume_row } } } } $_NFSVolumeOutput = $Script:_nfsvolumes | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""NFS"">NFS Volumes (Azure NetApp Files & Azure Files)</h2>" $script:_Content += "<a href=""#NFS"">NFS Volumes</a><br>" return $_NFSVolumeOutput } # collect ANF volume information function CollectANFVolumes { # if ANF parameters are defined it will query for ANF data if ($ANFAccountName -and $ANFResourceGroup) { # empty object for ANF volumes $script:_ANFVolumes = @() # get ANF Account $_ANFAccount = Get-AzNetAppFilesAccount -ResourceGroupName $ANFResourceGroup -Name $ANFAccountName if ($_ANFAccount.Count -gt 0) { WriteRunLog -category "INFO" -message "ANF Account $ANFAccountName found" } else { WriteRunLog -category "ERROR" -message "ANF Account $ANFAccountName not found" exit } # get all ANF Pools in ANF Account $_ANFPools = Get-AzNetAppFilesPool -ResourceGroupName $ANFResourceGroup -AccountName $_ANFAccount.Name if ($_ANFPools.Count -gt 0) { $_poolcount = $_ANFPools.Count WriteRunLog -category "INFO" -message "ANF Pools found: $_poolcount" } else { WriteRunLog -category "ERROR" -message "No ANF Pools not found" exit } # loop through pools foreach ($_ANFpool in $_ANFPools) { # the poolname is inside the string (ANFAccountName/ANFPoolName) # remove the ANF Accodunt name #$_ANFPoolName = $_ANFpool.Name -replace $_ANFAccount.Name,'' # remove the '/' from the string #$_ANFPoolName = $_ANFPoolName -replace '/','' $_ANFpool_split = $_ANFpool.Name.Split("/") $_ANFPoolName = $_ANFpool_split[1] $_ANFVolumesInPool = Get-AzNetAppFilesVolume -ResourceGroupName $ANFResourceGroup -AccountName $ANFAccountName -PoolName $_ANFPoolName foreach ($_ANFVolume in $_ANFVolumesInPool) { $_ANFVolume_row = "" | Select-Object Name,Pool,ServiceLevel,ThroughputMibps,ProtocolTypes,NFSAddress,QoSType,Id $_ANFVolume_row.Id = $_ANFVolume.Id $_ANFVolume_row.Name = ($_ANFVolume.Name -split '/')[2] $_ANFVolume_row.Pool = $_ANFPoolName $_ANFVolume_row.ServiceLevel = $_ANFVolume.ServiceLevel $_ANFVolume_row.ProtocolTypes = [string]$_ANFVolume.ProtocolTypes # $_ANFVolume_row.ThroughputMibps = [int]$_ANFVolume.ThroughputMibps $_ANFVolume_row.ThroughputMibps = (([int]$_ANFVolume.ThroughputMibps, [int]$_ANFVolume.ActualThroughputMibps) | Measure-Object -Maximum).Maximum $_ANFVolume_row.QoSType = $_ANFPool.QosType # $_ANFVolume_row.NFSAddress = $_ANFVolume.MountTargets[0].IpAddress + ":/" + $_ANFVolume_row.Name if ($_ANFVolume.MountTargets.Count -gt 0){ $_ANFVolume_row.NFSAddress = $_ANFVolume.MountTargets[0].IpAddress + ":/" + $_ANFVolume.CreationToken } $Script:_ANFVolumes += $_ANFVolume_row } } $_linecounter = 0 foreach ($_filesystem_row in $script:_filesystems) { if ($_filesystem_row.fstype -contains "nfs") { $script:_filesystems[$_linecounter].MaxMBPS = ($script:_ANFVolumes | Where-Object {$_.NFSAddress -eq $_filesystem_row.Source}).THROUGHPUTMIBPS } $_linecounter += 1 } $_ANFVolumesOutput = $Script:_ANFVolumes | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""ANF"">Azure NetApp Files</h2>" $script:_Content += "<a href=""#ANF"">Azure NetApp Files</a><br>" return $_ANFVolumesOutput } else { return "" } } function CollectFooter { $_Footer = @() $_Footer_row = "" | Select-Object Parameter $_Footer_row.Parameter = "<subscriptionid>$script:_SubscriptionID</subscriptionid>" $_Footer += $_Footer_row $_Footer_row = "" | Select-Object Parameter $_Footer_row.Parameter = "<subscriptionname>$script:_SubscriptionName</subscriptionname>" $_Footer += $_Footer_row $_Footer_row = "" | Select-Object Parameter $_Footer_row.Parameter = "<vmname>$script:_VMName</vmname>" $_Footer += $_Footer_row $_FooterContent = $_Footer | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""SupportInfo"">Info for Support Cases</h2>" return $_FooterContent } function CheckOSPrompt { # empty the buffer $script:_sshstream.read() | Out-Null # sending an "echo 1" $script:_sshstream.WriteLine("cat /etc/hostname") while (!$script:_sshstream.DataAvailable) { Start-Sleep -Milliseconds ($script:_sshstreamwait * 3) } $_output = $script:_sshstream.read() WriteDetailedDebugLog("Result:") WriteDetailedDebugLog($_output) $_output = $_output.Replace("`r","") WriteDetailedDebugLog("Result:1") WriteDetailedDebugLog($_output) $_array = $_output -split "`n" WriteDetailedDebugLog("Result:") WriteDetailedDebugLog($_array) # get last line # first line is command # second line is output of echo # third line is OS prompt $script:_osprompt = $_array[2] WriteDetailedDebugLog("Result:") WriteDetailedDebugLog($script:_osprompt) WriteRunLog -category "INFO" -message ("OS Prompt: " + $script:_osprompt) } function CheckSudoPermission { # Check is user is able to sudo if ($VMUsername -ne "root") { # $_command = PrepareCommand -Command "id" -CommandType "OS" -RootRequired $true # $_rootrights = RunCommand -p $_command $_command = "sudo bash" $_result = Invoke-SSHStreamExpectSecureAction -ShellStream $script:_sshstream -Command $_command -ExpectRegex '\[sudo\].*' -SecureAction $VMPassword $_command = "id" $_rootrights = Invoke-SSHStreamShellCommand -ShellStream $script:_sshstream -Command $_command # if ($_rootrights.Contains("root")) { $_foundroot = $_rootrights | Select-String -Pattern "(root)" if ($_foundroot) { # everything ok WriteRunLog -category "INFO" -message "User can sudo" $script:_CheckSudo = $true } else { WriteRunLog -category "ERROR" -message "User not able to sudo, please check sudoers file" WriteRunLog -category "ERROR" -message "Output of sudo check:" WriteRunLog -category "ERROR" -message "$_rootrights" $script:_CheckSudo = $false exit } } } function CheckFeatures { # create empty array $_outputarray = @() $script:_InstalledFeatures = @() # looping through feature checks foreach ($_FeatureCheck in $_jsonconfig.CheckFeatures) { # check if Feature tasks needs to be executed against this OS and database if ( $_FeatureCheck.OS.Contains($VMOperatingSystem) -and ` $_FeatureCheck.DB.Contains($VMDatabase) -and ` $_FeatureCheck.Role.Contains($VMRole) -and ` ($_FeatureCheck.OSVersion.Contains("all") -or $_FeatureCheck.OSVersion.Contains($VMOSRelease)) -and ` $_FeatureCheck.Hardwaretype.Contains($Hardwaretype)) { # check if check applies to HA or not and if HA check for HA-Agent if (($_FeatureCheck.HighAvailability.Contains($HighAvailability)) -or (($_FeatureCheck.HighAvailability.Contains($HighAvailability)) -and ($_FeatureCheck.HighAvailabilityAgent.Contains($HighAvailabilityAgent)))) { if ((-not $RunLocally) -or ($RunLocally -and ($_FeatureCheck.RunInLocalMode))) { $_output = RunCommand -p $_FeatureCheck # check if result should be shown in report if ($_FeatureCheck.ShowInReport) { # create a new empty object per line $_outputarray_row = "" | Select-Object CheckID, Description, Output $_outputarray_row.CheckID = $_FeatureCheck.FeatureId $_outputarray_row.Description = $_FeatureCheck.Description $_outputarray_row.Output = $_output -join ';;:;;' if ($_output -eq "Installed") { $script:_InstalledFeatures += $_FeatureCheck.FeatureName } # add line to outputarray $_outputarray += $_outputarray_row } } } } } # create HTML output $_outputarray = $_outputarray | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""FeatureCheck"">Feature Check</h2>This section checks for installed software and features" $_outputarray = $_outputarray.Replace(";;:;;","<br/>") # add Features to index $script:_Content += "<a href=""#FeatureCheck"">Installed Features and additional Software</a><br>" return $_outputarray } function CheckForNewerVersion { # download online version # and compare it with version numbers in files to see if there is a newer version available on GitHub $ConfigFileUpdateURL = "https://raw.githubusercontent.com/Azure/SAP-on-Azure-Scripts-and-Utilities/main/QualityCheck/version.json" try { $OnlineFileVersion = (Invoke-WebRequest -Uri $ConfigFileUpdateURL -UseBasicParsing -ErrorAction SilentlyContinue).Content | ConvertFrom-Json if ($OnlineFileVersion.Version -gt $scriptversion) { WriteRunLog -category "WARNING" -message "There is a newer version of QualityCheck available on GitHub, please consider downloading it" WriteRunLog -category "WARNING" -message "You can download it on https://github.com/Azure/SAP-on-Azure-Scripts-and-Utilities/tree/main/QualityCheck" WriteRunLog -category "WARNING" -message "Script will continue" Start-Sleep -Seconds 3 } } catch { WriteRunLog -category "WARNING" -message "Can't connect to GitHub to check version" } if (-not $RunLocally) { WriteRunLog -category "INFO" -message "Script Version $scriptversion" } $_scriptdate = [DateTime]::ParseExact($scriptversion, 'yyyyMMddHH', (Get-Culture)) $_currentDate_minus120 = (Get-Date).AddDays(-120) if ($_scriptdate -lt $_currentDate_minus120) { WriteRunLog -category "ERROR" -message "You are running a script version that is older than 120 days, please update" } } function LoadGUI { # define XAML code for form [xml]$_XAML = @" <Window x:Class="QualityCheck.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:QualityCheck" mc:Ignorable="d" WindowStartupLocation="CenterScreen" Title="SAP on Azure - Quality Check" Height="800" Width="900"> <Grid Margin="0,0,-13,-70"> <Grid.ColumnDefinitions> <ColumnDefinition Width="582*"/> <ColumnDefinition Width="325*"/> </Grid.ColumnDefinitions> <Button x:Name="ButtonRun" Content="Run" HorizontalAlignment="Left" Margin="109,582,0,0" VerticalAlignment="Top" Width="75" Grid.Column="1" Height="20"/> <ComboBox x:Name="Database" HorizontalAlignment="Left" Margin="190,40,0,0" VerticalAlignment="Top" Width="250" SelectedIndex="0" Height="22"> <ComboBoxItem Content="HANA"/> <ComboBoxItem Content="MSSQL"/> <ComboBoxItem Content="Db2"/> <ComboBoxItem Content="Oracle"/> <ComboBoxItem Content="ASE"/> </ComboBox> <Label x:Name="LabelDatabase" Content="Database" HorizontalAlignment="Left" Margin="66,36,0,0" VerticalAlignment="Top" Height="26" Width="59"/> <ComboBox x:Name="OperatingSystem" HorizontalAlignment="Left" Margin="190,75,0,0" VerticalAlignment="Top" Width="250" SelectedIndex="0" Height="22"> <ComboBoxItem Content="SUSE"/> <ComboBoxItem Content="RedHat"/> <ComboBoxItem Content="Windows"/> <ComboBoxItem Content="OracleLinux"/> </ComboBox> <Label x:Name="LabelOperatingSystem" Content="Operating System" HorizontalAlignment="Left" Margin="66,71,0,0" VerticalAlignment="Top" Height="26" Width="104"/> <CheckBox x:Name="HighAvailability" Content="HighAvailability" HorizontalAlignment="Left" Margin="190,281,0,0" VerticalAlignment="Top" IsChecked="True" Height="15" Width="102"/> <ComboBox x:Name="HANAScenario" HorizontalAlignment="Left" Margin="64,138,0,0" VerticalAlignment="Top" Width="120" SelectedIndex="0" Grid.Column="1" Height="22"> <ComboBoxItem Content="OLTP"/> <ComboBoxItem Content="OLAP"/> <ComboBoxItem Content="OLTP-ScaleOut"/> <ComboBoxItem Content="OLAP-ScaleOut"/> </ComboBox> <Label x:Name="LabelHANAScenario" Content="HANA Scenario" HorizontalAlignment="Left" Margin="506,134,0,0" VerticalAlignment="Top" Grid.ColumnSpan="2" Height="26" Width="91"/> <ComboBox x:Name="Role" HorizontalAlignment="Left" Margin="190,109,0,0" VerticalAlignment="Top" Width="250" SelectedIndex="0" Height="22"> <ComboBoxItem Content="DB"/> <ComboBoxItem Content="ASCS"/> <ComboBoxItem Content="APP"/> </ComboBox> <Label x:Name="LabelRole" Content="Role" HorizontalAlignment="Left" Margin="66,105,0,0" VerticalAlignment="Top" Height="26" Width="33"/> <ComboBox x:Name="ResourceGroup" HorizontalAlignment="Left" Margin="190,180,0,0" VerticalAlignment="Top" Width="250" SelectedIndex="0" Height="22" IsTextSearchEnabled="True"> </ComboBox> <Label x:Name="LabelResourceGroup" Content="Resource Group" HorizontalAlignment="Left" Margin="66,176,0,0" VerticalAlignment="Top" Height="26" Width="95"/> <ComboBox x:Name="VM" HorizontalAlignment="Left" Margin="190,211,0,0" VerticalAlignment="Top" Width="250" SelectedIndex="0" Height="22"/> <Label x:Name="LabelVM" Content="VM" HorizontalAlignment="Left" Margin="66,207,0,0" VerticalAlignment="Top" Height="26" Width="28"/> <TextBox x:Name="SSHPort" HorizontalAlignment="Left" Height="22" Margin="64,478,0,0" TextWrapping="Wrap" Text="22" VerticalAlignment="Top" Width="120" Grid.Column="1"/> <Label x:Name="LabelSSHPort" Content="SSH Port" HorizontalAlignment="Left" Margin="506,476,0,0" VerticalAlignment="Top" Grid.ColumnSpan="2" Width="125" Height="26"/> <TextBox x:Name="Username" HorizontalAlignment="Left" Height="23" Margin="190,501,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120"/> <Label x:Name="LabelUsername" Content="Username" HorizontalAlignment="Left" Margin="66,498,0,0" VerticalAlignment="Top" Height="26" Width="63"/> <Label x:Name="LabelPassword" Content="Password" HorizontalAlignment="Left" Margin="66,529,0,0" VerticalAlignment="Top" Height="26" Width="60"/> <PasswordBox x:Name="Password" HorizontalAlignment="Left" Margin="190,532,0,0" VerticalAlignment="Top" Width="145" Height="23"/> <TextBox x:Name="DBDataDir" HorizontalAlignment="Left" Height="23" Margin="64,40,0,0" TextWrapping="Wrap" Text="/hana/data" VerticalAlignment="Top" Width="120" Grid.Column="1"/> <Label x:Name="LabelDBDataDir" Content="DB Data Directory" HorizontalAlignment="Left" Margin="506,37,0,0" VerticalAlignment="Top" Grid.ColumnSpan="2" Height="26" Width="105"/> <TextBox x:Name="DBLogDir" HorizontalAlignment="Left" Height="23" Margin="64,74,0,0" TextWrapping="Wrap" Text="/hana/log" VerticalAlignment="Top" Width="120" Grid.Column="1"/> <Label x:Name="LabelDBLogDir" Content="DB Log Directory" HorizontalAlignment="Left" Margin="506,71,0,0" VerticalAlignment="Top" Grid.ColumnSpan="2" Height="26" Width="100"/> <ComboBox x:Name="HardwareType" HorizontalAlignment="Left" Margin="190,140,0,0" VerticalAlignment="Top" Width="250" SelectedIndex="0" Height="22"> <ComboBoxItem Content="VM"/> <ComboBoxItem Content="HLI"/> </ComboBox> <Label x:Name="LabelHardwareType" Content="Hardware Type" HorizontalAlignment="Left" Margin="66,136,0,0" VerticalAlignment="Top" Height="26" Width="89"/> <ComboBox x:Name="HighAvailabilityAgent" HorizontalAlignment="Left" Margin="190,311,0,0" VerticalAlignment="Top" Width="250" SelectedIndex="0" Height="22"> <ComboBoxItem Content="SBD"/> <ComboBoxItem Content="FencingAgent"/> <ComboBoxItem Content="WindowsCluster"/> </ComboBox> <Label x:Name="LabelHighAvailabilityAgent" Content="HA Agent" HorizontalAlignment="Left" Margin="66,307,0,0" VerticalAlignment="Top" Height="26" Width="61"/> <TextBox x:Name="DBSharedDir" HorizontalAlignment="Left" Height="23" Margin="64,106,0,0" TextWrapping="Wrap" Text="/hana/shared" VerticalAlignment="Top" Width="120" Grid.Column="1"/> <Label x:Name="LabelDBSharedDir" Content="DB Shared Directory" HorizontalAlignment="Left" Margin="506,103,0,0" VerticalAlignment="Top" Grid.ColumnSpan="2" Height="26" Width="117"/> <TextBox x:Name="hostname" HorizontalAlignment="Left" Height="23" Margin="190,241,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="249"/> <Label x:Name="LabelHostname" Content="Hostname/IP" HorizontalAlignment="Left" Margin="66,238,0,0" VerticalAlignment="Top" Height="26" Width="79"/> <TextBlock HorizontalAlignment="Left" Margin="66,582,0,0" TextWrapping="Wrap" Text=" If you want to find out more about SAP Quality Check " VerticalAlignment="Top" Width="477" Height="16"> <Hyperlink NavigateUri="https://github.com/Azure/SAP-on-Azure-Scripts-and-Utilities/tree/main/QualityCheck"> <Hyperlink.Inlines> <Run Text="Click here"/> </Hyperlink.Inlines> </Hyperlink> </TextBlock> <Button x:Name="ButtonExit" Content="Exit" HorizontalAlignment="Left" Margin="22,582,0,0" VerticalAlignment="Top" Width="75" Height="20" Grid.Column="1"/> <ComboBox x:Name="LogonMethod" HorizontalAlignment="Left" Margin="190,463,0,0" VerticalAlignment="Top" Width="250" SelectedIndex="0" Height="22"> <ComboBoxItem Content="UserPassword"/> </ComboBox> <Label x:Name="LabelHighAvailabilityAgent_Copy" Content="Logon Method" HorizontalAlignment="Left" Margin="66,461,0,0" VerticalAlignment="Top" Height="26" Width="89"/> <ComboBox x:Name="DiskType" HorizontalAlignment="Left" Margin="190,349,0,0" VerticalAlignment="Top" Width="250" SelectedIndex="0" Height="22"> <ComboBoxItem Content="ANF"/> <ComboBoxItem Content="Managed Disk"/> <ComboBoxItem Content="xNFS"/> <ComboBoxItem Content="Shared Disk"/> <ComboBoxItem Content="File Share"/> </ComboBox> <Label x:Name="LabelDiskType" Content="Disk Type" HorizontalAlignment="Left" Margin="66,349,0,0" VerticalAlignment="Top" Height="26" Width="61"/> <ComboBox x:Name="ANFResourceGroup" HorizontalAlignment="Left" Margin="190,388,0,0" VerticalAlignment="Top" Width="250" SelectedIndex="0" Height="22" IsTextSearchEnabled="True"/> <Label x:Name="LabelANFResourceGroup" Content="ANF Resource Group" HorizontalAlignment="Left" Margin="66,384,0,0" VerticalAlignment="Top" Height="26" Width="124"/> <ComboBox x:Name="ANFAccountName" HorizontalAlignment="Left" Margin="190,424,0,0" VerticalAlignment="Top" Width="250" SelectedIndex="0" Height="22"/> <Label x:Name="LabelANFAccountName" Content="ANF Account Name" HorizontalAlignment="Left" Margin="66,420,0,0" VerticalAlignment="Top" Height="26" Width="119"/> </Grid> </Window> "@ -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window' #-replace wird benötigt, wenn XAML aus Visual Studio kopiert wird. #XAML laden [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') try{ $_Form=[Windows.Markup.XamlReader]::Load( (New-Object System.Xml.XmlNodeReader $_XAML) ) } catch { Write-Host "Windows.Markup.XamlReader konnte nicht geladen werden. Mögliche Ursache: ungültige Syntax oder fehlendes .net" } # check if connected to Azure if ($SubscriptionId) { $_SubscriptionInfo = Get-AzSubscription -SubscriptionId $SubscriptionId } else { $_SubscriptionInfo = Get-AzSubscription } # if $_SubscritpionInfo then it got subscriptions if ($_SubscriptionInfo) { # connected } else { WriteRunLog -category "ERROR" -message "Please connect to Azure using the Connect-AzAccount command, if you are connected use the Select-AzSubscription command to set the correct context" exit } $_database = $_Form.FindName("Database") $_database.add_SelectionChanged( { param($sender,$args) $selected = $sender.SelectedItem.Content if ($selected -eq "HANA") { $_Form.FindName("LabelHANAScenario").Visibility = "Visible" $_Form.FindName("HANAScenario").Visibility = "Visible" } else { $_Form.FindName("LabelHANAScenario").Visibility = "Hidden" $_Form.FindName("HANAScenario").Visibility = "Hidden" } #if ($_database.SelectionBoxItem.Equals("HANA")) { # $_Form.FindName("LabelHANAScenario").Visibility = "Visible" #} #else { # $_Form.FindName("LabelHANAScenario").Visibility = "Hidden" #} } ) $_highavailability = $_Form.FindName("HighAvailability") $_highavailability.Add_Click( { param($sender,$args) $selected = $sender.isChecked if ($selected -eq $true) { $_Form.FindName("LabelHighAvailabilityAgent").Visibility = "Visible" $_Form.FindName("HighAvailabilityAgent").Visibility = "Visible" } else { $_Form.FindName("LabelHighAvailabilityAgent").Visibility = "Hidden" $_Form.FindName("HighAvailabilityAgent").Visibility = "Hidden" } } ) $_disktype = $_Form.FindName("DiskType") $_disktype.add_SelectionChanged( { param($sender,$args) $selected = $sender.SelectedItem.Content if ($selected -eq "ANF") { $_Form.FindName("LabelANFResourceGroup").Visibility = "Visible" $_Form.FindName("LabelANFAccountName").Visibility = "Visible" $_Form.FindName("ANFResourceGroup").Visibility = "Visible" $_Form.FindName("ANFAccountName").Visibility = "Visible" } else { $_Form.FindName("LabelANFResourceGroup").Visibility = "Hidden" $_Form.FindName("LabelANFAccountName").Visibility = "Hidden" $_Form.FindName("ANFResourceGroup").Visibility = "Hidden" $_Form.FindName("ANFAccountName").Visibility = "Hidden" } } ) $_ButtonExit = $_Form.FindName("ButtonExit") $_ButtonExit.Add_Click( { $_Form.Close() exit } ) $_GUI_ResourceGroups = $_Form.FindName("ResourceGroup") # add resource groups $_ResourceGroups = Get-AzResourceGroup | Select-Object ResourceGroupName | Sort-Object $_GUI_ResourceGroups.ItemsSource = $_ResourceGroups.ResourceGroupName | Sort-Object #foreach ($_resourcegroup in $_ResourceGroups) { # [void]$_GUI_ResourceGroups.Items.Add($_resourcegroup.ResourceGroupName) #} $_GUI_VMs = $_Form.FindName("VM") $_GUI_ResourceGroups.add_SelectionChanged( { # add VMs $_GUI_VMs.Items.Clear() $_VMs = Get-AzVM -ResourceGroupName $_GUI_ResourceGroups.Items[$_GUI_ResourceGroups.SelectedIndex] foreach ($_VM in $_VMs) { $_GUI_VMs.Items.Add($_VM.Name) } } ) $_GUI_IPaddress = $_Form.FindName("hostname") $_GUI_VMs.add_SelectionChanged( { # get IP of first nic $_VM = Get-AzVM -ResourceGroupName $_GUI_ResourceGroups.Items[$_GUI_ResourceGroups.SelectedIndex] -Name $_GUI_VMs.Items[$_GUI_VMs.SelectedIndex] #$_NetworkInterface = Get-AzNetworkInterfaceIpConfig -NetworkInterface $_VM.NetworkProfile.NetworkInterfaces $_GUI_IPaddress.Text = (Get-AzNetworkInterface -resourceid $_VM.NetworkProfile.NetworkInterfaces.Id)[0].IpConfigurations[0].PrivateIpAddress } ) $_GUI_ANFResourceGroups = $_Form.FindName("ANFResourceGroup") # add ANF resource group $_ANFResourceGroups = Get-AzResourceGroup | Select-Object ResourceGroupName | Sort-Object $_GUI_ANFResourceGroups.ItemsSource = $_ANFResourceGroups.ResourceGroupName | Sort-Object $_GUI_ANFAccountName = $_Form.FindName("ANFAccountName") $_GUI_ANFResourceGroups.add_SelectionChanged( { # add ANFs $_GUI_ANFAccountName.Items.Clear() $_ANFs = Get-AzNetAppFilesAccount -ResourceGroupName $_GUI_ANFResourceGroups.Items[$_GUI_ANFResourceGroups.SelectedIndex] foreach ($_ANF in $_ANFs) { $_GUI_ANFAccountName.Items.Add($_ANF.Name) } } ) # add Run button $_ButtonRun = $_Form.FindName("ButtonRun") $_ButtonRun.Add_Click( { # when "RUN" button is pressed $_gui_password_value = $_Form.FindName("Password").Password $script:VMUsername = $_Form.FindName("Username").Text $script:VMPassword = ConvertTo-SecureString -String $_gui_password_value -AsPlainText -Force $script:VMHostname = $_Form.FindName("hostname").Text $script:VMDatabase = $_Form.FindName("Database").Items[$_Form.FindName("Database").SelectedIndex].Content $script:VMOperatingSystem = $_Form.FindName("OperatingSystem").Items[$_Form.FindName("OperatingSystem").SelectedIndex].Content $script:Hardwaretype = $_Form.FindName("HardwareType").Items[$_Form.FindName("HardwareType").SelectedIndex].Content $script:AzVMResourceGroup = $_Form.FindName("ResourceGroup").Items[$_Form.FindName("ResourceGroup").SelectedIndex] $script:AzVMName = $_Form.FindName("VM").Items[$_Form.FindName("VM").SelectedIndex] $script:DBDataDir = $_Form.FindName("DBDataDir").Text $script:DBLogDir = $_Form.FindName("DBLogDir").Text $script:DBSharedDir = $_Form.FindName("DBSharedDir").Text $script:HANADeployment = $_Form.FindName("HANAScenario").Items[$_Form.FindName("HANAScenario").SelectedIndex].Content $script:VMRole = $_Form.FindName("Role").Items[$_Form.FindName("Role").SelectedIndex].Content $script:VMConnectionPort = $_Form.FindName("SSHPort").Text if ($_Form.FindName("HighAvailability").IsChecked) { $script:HighAvailability = $true $script:HighAvailabilityAgent = $_Form.FindName("HighAvailabilityAgent").Items[$_Form.FindName("HighAvailabilityAgent").SelectedIndex].Content } else { $script:HighAvailability = $false } if ($_Form.FindName("DiskType").Items[$_Form.FindName("DiskType").SelectedIndex].Content -eq "ANF") { $script:ANFResourceGroup = $_Form.FindName("ANFResourceGroup").Items[$_Form.FindName("ANFResourceGroup").SelectedIndex] $script:ANFAccountName = $_Form.FindName("ANFAccountName").Items[$_Form.FindName("ANFAccountName").SelectedIndex] } $script:GUILogonMethod = $_Form.FindName("LogonMethod").Items[$_Form.FindName("LogonMethod").SelectedIndex].Content $_Form.Close() } ) #$_Form.Add_Closing({param($sender,$e) # $script:_CloseButtonPressed = $true #}) #show dialog [void]$_Form.ShowDialog() } # Set Linux OS Prompt function SetOSPrompt { $_command = PrepareCommand -Command "PS1='QualityCheck# '" -CommandType "OS" $_output = RunCommand -p $_command } ######### # Main module ######### $script:_runlog = @() $script:_WriteDetailedDebugFileName = "DebugLog-" + $(Get-Date -Format "yyyyMMdd-HHmm") + ".txt" $_breakingchangewarning = Get-AzConfig -DisplayBreakingChangeWarning if ($_breakingchangewarning.Value -eq $true) { Update-AzConfig -DisplayBreakingChangeWarning $false } WriteRunLog -category "INFO" -message ("Start " + (Get-Date)) WriteRunLog -category "INFO" -message "Quality Check for SAP on Azure systems is provided under MIT license" WriteRunLog -category "INFO" -message "see https://github.com/Azure/SAP-on-Azure-Scripts-and-Utilities/blob/main/LICENSE for details" if ($LogonWithUserSSHKey -or $LogonAsRootSSHKey) { $script:VMPassword = ConvertTo-SecureString -String "dummypw" -AsPlainText -Force } # load json configuration $_jsonconfig = Get-Content -Raw -Path $ConfigFileName -ErrorAction Stop | ConvertFrom-Json if ($scriptversion -eq $_jsonconfig.Version) { # everything ok, script and json version match } else { WriteRunLog -category "ERROR" -message "Versions of script and json file don't match" exit } $script:_CloseButtonPressed = $false if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting GUI ...") } if ($GUI) { if ($IsWindows) { LoadGUI if ($script:_CloseButtonPressed) { exit } } else { WriteRunLog -category "ERROR" -message "Sorry, GUI is only supported on Windows Systems" exit } } # read file in case of multiple runs if ($MultiRun) { if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Detected Multirun setting") } if (Test-Path -Path $ImportFile -PathType Leaf) { WriteRunLog -category "INFO" -message "Reading file for Quality Check runs" # read config file $_MultiRunData = Get-Content -Raw -Path $ImportFile | ConvertFrom-Csv -Delimiter ";" WriteRunLog -category "INFO" -message ("Read " + $_MultiRunData.Count + " entries in file") $script:VMPassword = read-host "Enter Password: " -asSecureString } } if (-not $MultiRun) { # single run # generate dummy structure if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Detected Single VM setting") } $_MultiRunData = @() $_MultiRunData_Row = "" | Select-Object Data1 $_MultiRunData_Row.Data1 = "onlysinglerun" $_MultiRunData += $_MultiRunData_Row } foreach ($_qcrun in $_MultiRunData) { if ($MultiRun) { # copy values to variables $script:_runlog = @() $script:VMUsername = $_qcrun.VMUsername $script:VMOperatingSystem = $_qcrun.VMOperatingSystem $script:VMDatabase = $_qcrun.VMDatabase $script:VMRole = $_qcrun.VMRole $script:VMHostname = $_qcrun.VMHostname $script:AzVMResourceGroup = $_qcrun.AzVMResourceGroup $script:AzVMName = $_qcrun.AzVMName if ($_qcrun.DBDataDir) { $script:DBDataDir = $_qcrun.DBDataDir } if ($_qcrun.DBLogDir) { $script:DBLogDir = $_qcrun.DBLogDir } if ($_qcrun.DBSharedDir) { $script:DBSharedDir = $_qcrun.DBSharedDir } if ($_qcrun.HANADeployment) { $script:HANADeployment = $_qcrun.HANADeployment } if ($_qcrun.ANFResourceGroup) { $script:ANFResourceGroup = $_qcrun.ANFResourceGroup } if ($_qcrun.ANFAccountName) { $script:ANFAccountName = $_qcrun.ANFAccountName } if ($_qcrun.HighAvailability -eq "TRUE") { $script:HighAvailability = $true } else { $script:HighAvailability = $false } if ($_qcrun.HighAvailabilityAgent) { $script:HighAvailabilityAgent = $_qcrun.HighAvailabilityAgent } switch($_qcrun.LogonMethod) { "UserPassword" { $script:GUILogonMethod = "UserPassword" } Default { $script:GUILogonMethod = "NoMatch" } } } else { # nothing to do, variables already populated } # parameter check and modification if required if ($VMOperatingSystem -in @("SUSE","RedHat","OracleLinux")) { #check if filesystem parameters end with / if ($script:DBDataDir.EndsWith("/")) { $script:DBDataDir = $script:DBDataDir.Substring(0,$script:DBDataDir.Length-1) } if ($script:DBLogDir.EndsWith("/")) { $script:DBLogDir = $script:DBLogDir.Substring(0,$script:DBLogDir.Length-1) } if ($script:DBSharedDir.EndsWith("/")) { $script:DBSharedDir = $script:DBSharedDir.Substring(0,$script:DBSharedDir.Length-1) } } if (-not $RunLocally) { # Check for required PowerShell modules if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CheckRequiredModules") } CheckRequiredModules # Check for newer version of QualityCheck if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CheckForNewerVersion") } CheckForNewerVersion # Check Azure connectivity if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CheckAzureConnectivity") } CheckAzureConnectivity # Check TCP connectivity if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CheckTCPConnectivity") } CheckTCPConnectivity # Connect to VM if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting ConnectVM") } $_SessionID = ConnectVM # Check if user is able to sudo if ($script:_ConnectVMResult) { if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CheckSudoPermission") } CheckSudoPermission } else { $script:_CheckSudo = $false } if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting SetOSPrompt") } SetOSPrompt # getting OS prompt for verifications # CheckOSPrompt } if ($HighAvailability) { $_sbdcommand = PrepareCommand -Command "cat /etc/sysconfig/sbd | grep ^SBD_DEVICE | wc -l" -CommandType "OS" -RootRequired $true $_sbdconfig = RunCommand -p $_sbdcommand if ($_sbdconfig -eq "0") { # SBD Device config not found $HighAvailabilityAgent = "FencingAgent" } else { $HighAvailabilityAgent = "SBD" } } # Load HTML if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting LoadHTMLHeader") } LoadHTMLHeader # Collect PowerShell Parameters if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting Parameter Dump") } $_ParameterValues = @{} $_ParametersToIgnore = @("Verbose", "Debug", "ErrorAction", "WarningAction", "InformationAction", "ErrorVariable", "WarningVariable", "InformationVariable", "OutVariable", "OutBuffer", "PipelineVariable") foreach ($_parameter in $MyInvocation.MyCommand.Parameters.GetEnumerator()) { try { $_key = $_parameter.Key if($null -ne ($_value = Get-Variable -Name $_key -ValueOnly -ErrorAction Ignore)) { if($value -ne ($null -as $_parameter.Value.ParameterType)) { $_ParameterValues[$_key] = $_value } } if($PSBoundParameters.ContainsKey($_key)) { $_ParameterValues[$_key] = $PSBoundParameters[$_key] } if (-not ($_ParametersToIgnore -contains $_key) ) { WriteRunLog -category "INFO" -message "Parameter $_key : $_value" } } finally {} } # Collect Script Parameters if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CollectScriptParameters") } $_CollectScriptParameter = CollectScriptParameters # Collect PowerShell details if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CollectPowerShellDetails") } $_CollectPowerShell = CollectPowerShellDetails # Collecct Features and Installed Software if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CheckFeatures") } $_Features = CheckFeatures # Collect VM info if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CollectVMInformation") } $_CollectVMInfo = CollectVMInformation # Get Azure Disks assigned to VMs if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CollectVMStorage") } $_CollectVMStorage = CollectVMStorage # Get Volume Groups - CollectVMStorage needs to run first to define variables if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CollectLVMGroups") } $_CollectLVMGroups = CollectLVMGroups # Get Logical Volumes - CollectVMStorage needs to run first to define variables if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CollectLVMVolummes") } $_CollectLVMVolumes = CollectLVMVolummes if (-not $RunLocally) { # Get ANF and AFS Volumes (NFS) if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CollectNFSVolumes") } $_CollectNFSVolumes = CollectNFSVolumes # Get ANF Volume Info #if ($DetailedLog) { # WriteRunLog -category "INFO" -message ("Starting CollectANFVolumes") #} #$_CollectANFVolumes = CollectANFVolumes } # Get Filesystems if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CollectFileSystems") } $_CollectFileSystems = CollectFileSystems if (-not $RunLocally) { # Get Network Interfaces if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CollectNetworkInterfaces") } $_CollectNetworkInterfaces = CollectNetworkInterfaces # Get Load Balancer - CollectNetworkInterfaces needs to run first to define variables if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CollectLoadBalancer") } $_CollectLoadBalancer = CollectLoadBalancer } # run Quality Check if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting RunQualityCheck") } $_RunQualityCheck = RunQualityCheck # Collect VM info if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CollectVMInformationAdditional") } $_CollectVMInfoAdditional = CollectVMInformationAdditional # Collect footer for support cases if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting CollectFooter") } $_CollectFooter = CollectFooter if (-not $RunLocally) { WriteRunLog -category "INFO" -message ("Creating HTML File: " + $_HTMLReportFileName) $_RunLogContent = $script:_runlog | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""RunLog"">RunLog</h2>" $_HTMLReport = ConvertTo-Html -Body "$_Content $_CollectScriptParameter $_CollectVMInfo $_RunQualityCheck $_CollectFileSystems $_CollectVMStorage $_Features $_CollectLVMGroups $_CollectLVMVolumes $_CollectNFSVolumes $_CollectANFVolumes $_CollectNetworkInterfaces $_CollectLoadBalancer $_CollectVMInfoAdditional $_CollectFooter $_RunLogContent $_CollectPowerShell" -Head $script:_HTMLHeader -Title "Quality Check for SAP Worloads on Azure" -PostContent "<p id='CreationDate'>Creation Date: $(Get-Date)</p><p id='CreationDate'>Script Version: $scriptversion</p>" $_HTMLReportFileDate = $(Get-Date -Format "yyyyMMdd-HHmm") $_HTMLReportFileName = $AzVMName + "-" + $_HTMLReportFileDate + ".html" if ([string]::IsNullOrEmpty($OutputDirName)) { $_HTMLReport | Out-File .\$_HTMLReportFileName } else { $_HTMLReport | Out-File $OutputDirName\$_HTMLReportFileName } if ($AddJSONFile) { # adding JSONfile $_jsonoutput = "" | Select-Object Rundate, VMName, ResourceGroup, HighAvailability, Checks, Parameters, InformationCollection, Disks, Filesystems, RunLog, Filename, QCVersion, Features WriteRunLog -category "INFO" -message ("Preparing JSON Output") $_jsonoutput.Checks = $script:_Checks $_jsonoutput.Parameters = $_ParameterValues $_jsonoutput.Disks = $script:_AzureDisks $_jsonoutput.Filesystems = $script:_filesystems $_jsonoutput.RunLog = $script:_runlog $_jsonoutput.Rundate = (Get-Date) $_jsonoutput.VMName = $AzVMName $_jsonoutput.ResourceGroup = $AzVMResourceGroup $_jsonoutput.HighAvailability = $HighAvailability $_jsonoutput.QCVersion = $scriptversion $_jsonoutput.Features = $_Features $_JSONReportFileName = $AzVMName + "-" + $_HTMLReportFileDate + ".json" $_jsonoutput.Filename = $_JSONReportFileName $_jsonoutput = $_jsonoutput | ConvertTo-Json -Depth 10 if ([string]::IsNullOrEmpty($OutputDirName)) { $_jsonoutput | Out-File .\$_JSONReportFileName } else { $_jsonoutput | Out-File $OutputDirName\$_JSONReportFileName } } } else { # script running locally, convert result to JSON $_jsonoutput = "" | Select-Object Rundate, VMName, ResourceGroup, HighAvailability, Checks, Parameters, InformationCollection, Disks, Filesystems, RunLog WriteRunLog -category "INFO" -message ("Preparing JSON Output") WriteRunLog -category "INFO" -message ("End " + (Get-Date)) $_jsonoutput.Checks = $script:_Checks $_jsonoutput.Parameters = $_ParameterValues $_jsonoutput.Disks = $script:_AzureDisks $_jsonoutput.Filesystems = $script:_filesystems $_jsonoutput.RunLog = $script:_runlog $_jsonoutput.Rundate = (Get-Date) $_jsonoutput.VMName = $AzVMName $_jsonoutput.ResourceGroup = $AzVMResourceGroup $_jsonoutput.HighAvailability = $HighAvailability $_jsonoutput = $_jsonoutput | ConvertTo-Json -Depth 10 Write-Host $_jsonoutput } if ((-not $RunLocally) -and $script:_ConnectVMResult) { Remove-SSHSession -SessionId $_SessionID | Out-Null } } # load report in browser for GUI runs if ($GUI) { if ($DetailedLog) { WriteRunLog -category "INFO" -message ("Starting Browser with report") } &(".\" + $_HTMLReportFileName) } if (-not $RunLocally) { WriteRunLog -category "INFO" -message ("End " + (Get-Date)) } if ($_breakingchangewarning.Value -eq $true) { Update-AzConfig -DisplayBreakingChangeWarning $true } exit