QualityCheck.txt.ps1 (2,447 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 table { font-size: 12px; border: 0px; font-family: Lucida Console, monospace; #> <# 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 ) # defining script version $scriptversion = 2022082301 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: Calibri (Body); } 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>Use the links to jump to the sections:</h2>" } # RunLog function for more detailed data during execution function WriteRunLog { [CmdletBinding()] param ( [string]$message, [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 } } # 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+',',' # 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+',',' # create table object $_x | Foreach-Object { $_output += $_ | ConvertFrom-Csv -Header target,source,fstype,options } # 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 } } 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 { if ($script:LogonWithUserPassword -or (($script:GUILogonMethod -eq "UserPassword") -and $GUI )) { # 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 $script:_sshsession = New-SSHSession -ComputerName $VMHostname -Credential $_credentials -Port $VMConnectionPort -AcceptKey -ConnectionTimeout 5 -ErrorAction SilentlyContinue } switch ($PsCmdlet.ParameterSetName) { "UserPassword" { } "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" { # 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" { # 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" { # 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" { # 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" { # 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" { $_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) { # return SSH session ID for later use $script:_ConnectVMResult = $true 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 CollectVMInformation { # create empty array $_outputarray = @() # 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 ((-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))) { $_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 ) # 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 # root permissions required? if (($p.RootRequired) -and ($VMUsername -ne "root") -and (-not $LogonWithUserSSHKey)) { # add sudo to the command $_command = "echo '$_ClearTextPassword' | sudo -E -S " + $p.ProcessingCommand } else { if ($LogonWithUserSSHKey) { 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 # just store theoutput of the command in $_result $_result = $_result.Output # 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 # 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 $_SubscriptionInfo = Get-AzSubscription # 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 } } 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, [string]$CommandType = "OS", [boolean]$RootRequired = $true, [string]$PostProcessingCommand = "" ) $_p = "" | Select-Object ProcessingCommand, CommandType, RootRequired, PostProcessingCommand $_p.ProcessingCommand = $Command $_p.CommandType = $CommandType $_p.RootRequired = $RootRequired $_p.PostProcessingCommand = $PostProcessingCommand return $_p } # Calculate Disk Name function CalculateDiskTypeSKU { param ( [int]$size, [string]$tier ) # check which storage tier is used $_performancetype = switch ($tier) { Premium_LRS { 'P' } UltraSSD_LRS { 'U' } Standard_LRS { 'S' } StandardSSD_LRS { 'E' } 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} } $_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 } # 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-11-01'" $script:_azurediskconfig = RunCommand -p $_command | ConvertFrom-Json # 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 -eq "/dev/disk/cloud/azure_root") { # backup if the virtual device doesn't exist $_rootdisk = "/dev/sda" } # 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 } catch { if (-not $RunLocally) { WriteRunLog -category "WARNING" -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 $_lsscsi_command = "lsscsi | sed 's/\[//; s/\]//; s/\.//' | sed 's/:/ /g' | grep Virtual | grep -v " + $_rootdisk + " | grep -v " + $_resourcedisk $_command = PrepareCommand -Command $_lsscsi_command -CommandType OS $script:_diskmapping = RunCommand -p $_command $script:_diskmapping = ConvertFrom-String_lsscsi -p $script:_diskmapping # 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 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) } try { $_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 "WARNING" -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 $script:_AzureDisks += $_AzureDisk_row } } if ($VMOperatingSystem -eq "Windows") { # Get information about physical disks $script:_datadisks = Invoke-Command -ComputerName $AzVMName -ScriptBlock {Get-Volume } # create HTML output $_FilesystemsOutput = $script:_datadisks | Select-Object DriveLetter, FileSystemLabel, OperationalStatus, HealthStatus, @{Name="Size (GB)";Expression={[math]::Round($_.Size/1GB,2)}}, @{Name="SizeRemaining (GB)";Expression={[math]::Round($_.SizeRemaining/1GB,2)}}, AllocationUnitSize | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""Logical Disks"">Logical Disks</h2>This section shows you the Logical Disks attached to the windows VM." # add entry in HTML index $script:_Content += "<a href=""#Logical Disks"">Logical Disks</a><br>" return $_FilesystemsOutput # return $_DatadisksOutput } else { # convert output to HTML $script:_AzureDisksOutput = $script:_AzureDisks | ConvertTo-Html -Property * -Fragment -PreContent "<br><h2 id=""VMStorage"">Collect VM Storage Information</h2>This table contains the disks directly attached to the VM" # add VM Storage to HTML index $script:_Content += "<a href=""#VMStorage"">VM Storage</a><br>" return $script:_AzureDisksOutput } } # 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 # 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 # 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 # add details $_loadbalancer_row.Name = $_loadbalancername $_loadbalancer_row.Type = $_loadbalancer.Sku $_loadbalancer_row.IdleTimeout = $_loadbalancer.LoadBalancingRules[0].IdleTimeoutInMinutes $_loadbalancer_row.FloatingIP = $_loadbalancer.LoadBalancingRules[0].EnableFloatingIP $_loadbalancer_row.Protocols = $_loadbalancer.LoadBalancingRules[0].Protocol # 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, AdditionalInfo, Testresult, ExpectedResult, Status, SAPNote, MicrosoftDocs } else { $_Check_row = "" | Select-Object CheckID, Description, AdditionalInfo, Testresult, ExpectedResult, Status, SAPNote, MicrosoftDocs, Success, VmRole } # 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 "INFO" $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 $DBDataDir}) if ($_filesystem_hana.Source.StartsWith("/dev/sd")) { $_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 $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 $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 $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 $DBDataDir} $_AzureDisks_hana = ($_AzureDisks | Where-Object { $_.DeviceName -in $_AzureDisks_for_hanadata_filesystems.Source}) } $_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 $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 $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 $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 if ($_filesystem_hana_type -eq "lvm") { foreach ($_AzureDisk_hana in $_AzureDisks_hana) { 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 } } } } 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 $DBLogDir}) if ($_filesystem_hana.Source.StartsWith("/dev/sd")) { $_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 $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 $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 $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 $DBLogDir} $_AzureDisks_hana = ($_AzureDisks | Where-Object { $_.DeviceName -in $_AzureDisks_for_hanalog_filesystems.Source}) } $_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 $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 $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 $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 } } } 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 $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" } } # 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 ((-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 ($RunLocally) { $_Check_row.VmRole = $VMRole } if ($_check.SAPNote -ne "") { if (-not $RunLocally) { $_Check_row.SAPNote = "::SAPNOTEHTML1::" + $_check.SAPNote + "::SAPNOTEHTML2::" + $_check.SAPNote + "::SAPNOTEHTML3::" } else { $_Check_row.SAPNote = $_check.SAPNote } } if ($_check.MicrosoftDocs -ne "") { if (-not $RunLocally) { $_Check_row.MicrosoftDocs = "::MSFTDOCS1::" + $_check.MicrosoftDocs + "::MSFTDOCS2::" + "Link" + "::MSFTDOCS3::" } else { $_Check_row.MicrosoftDocs = $_check.MicrosoftDocs } } 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 ($_check.ShowAlternativeResult -ne "") { $_Check_row.Testresult = Invoke-Expression $_check.ShowAlternativeResult } else { $_Check_row.Testresult = "" } if ($_check.ShowAlternativeRequirement -ne "") { $_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 '::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") { # future Windows code } else { # linux systems # run findmnt command on OS $_command = PrepareCommand -Command "findmnt -r -n" -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,Options,Size,Free,Used,UsedPercent,MaxMBPS,MaxIOPS,StripeSize $_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")) { # NFS ANF volumes need throughput values from ANF infos # $_filesystem_row.MaxMBPS = ($script:_ANFVolumes | Where-Object { $_.NFSAddress -eq $_filesystem_row.Source}).THROUGHPUTMIBPS $_filesystem_row.MaxMBPS = ($script:_ANFVolumes | Where-Object { $_filesystem_row.Source.StartsWith($_.NFSAddress) }).THROUGHPUTMIBPS } else { if ($_filesystem.Filesystem.StartsWith("/dev/sd")) { # 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 } 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 } } 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 # return $_DatadisksOutput } else { # 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 } else {} } # 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 } else {} } # 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 } else {} } # 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 # get all ANF Pools in ANF Account $_ANFPools = Get-AzNetAppFilesPool -ResourceGroupName $ANFResourceGroup -AccountName $_ANFAccount.Name # 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 '/','' $_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.QoSType = $_ANFPool.QosType # $_ANFVolume_row.NFSAddress = $_ANFVolume.MountTargets[0].IpAddress + ":/" + $_ANFVolume_row.Name $_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 CheckSudoPermission { # Check is user is able to sudo if ($VMUsername -ne "root") { $_command = PrepareCommand -Command "id" -CommandType "OS" -RootRequired $true $_rootrights = RunCommand -p $_command if ($_rootrights.Contains("root")) { # 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" $script:_CheckSudo = $false } } } 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" } } 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="600" Width="900"> <Grid Margin="0,0,-6.667,-29.333"> <Grid.ColumnDefinitions> <ColumnDefinition Width="582*"/> <ColumnDefinition Width="325*"/> </Grid.ColumnDefinitions> <Button x:Name="ButtonRun" Content="Run" HorizontalAlignment="Left" Margin="233,540,0,0" VerticalAlignment="Top" Width="75" Grid.Column="1" Height="20"/> <ComboBox x:Name="Database" HorizontalAlignment="Left" Margin="188,40,0,0" VerticalAlignment="Top" Width="250" SelectedIndex="0" Height="22"> <ComboBoxItem Content="HANA"/> <ComboBoxItem Content="MSSQL"/> <ComboBoxItem Content="Db2"/> <ComboBoxItem Content="Oracle"/> </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="188,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="191,289,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="188,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="188,180,0,0" VerticalAlignment="Top" Width="250" SelectedIndex="0" Height="22"> </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="188,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="Center" Height="22" Margin="0,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="191,435,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120"/> <Label x:Name="LabelUsername" Content="Username" HorizontalAlignment="Left" Margin="66,432,0,0" VerticalAlignment="Top" Height="26" Width="63"/> <Label x:Name="LabelPassword" Content="Password" HorizontalAlignment="Left" Margin="66,463,0,0" VerticalAlignment="Top" Height="26" Width="60"/> <PasswordBox x:Name="Password" HorizontalAlignment="Left" Margin="191,466,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="188,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="191,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="188,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="23,539,0,0" TextWrapping="Wrap" Text="If you want to find out more about 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="144,540,0,0" VerticalAlignment="Top" Width="75" Grid.Column="1" Height="20"/> <ComboBox x:Name="LogonMethod" HorizontalAlignment="Left" Margin="191,397,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,395,0,0" VerticalAlignment="Top" Height="26" Width="89"/> </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 $_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" } } ) $_ButtonExit = $_Form.FindName("ButtonExit") $_ButtonExit.Add_Click( { $_Form.Close() exit } ) $_GUI_ResourceGroups = $_Form.FindName("ResourceGroup") # add resource groups $_ResourceGroups = Get-AzResourceGroup 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).IpConfigurations.PrivateIpAddress } ) # 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 } $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() } ######### # Main module ######### $script:_runlog = @() 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 ($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 (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 $_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 ($DBDataDir.EndsWith("/")) { $DBDataDir = $DBDataDir.Substring(0,$DBDataDir.Length-1) } if ($DBLogDir.EndsWith("/")) { $DBLogDir = $DBLogDir.Substring(0,$DBLogDir.Length-1) } if ($DBSharedDir.EndsWith("/")) { $DBSharedDir = $DBSharedDir.Substring(0,$DBSharedDir.Length-1) } } if (-not $RunLocally) { # Check for required PowerShell modules CheckRequiredModules # Check for newer version of QualityCheck CheckForNewerVersion # Check Azure connectivity CheckAzureConnectivity # Check TCP connectivity CheckTCPConnectivity # Connect to VM $_SessionID = ConnectVM # Check if user is able to sudo if ($script:_ConnectVMResult) { CheckSudoPermission } else { $script:_CheckSudo = $false } } if ($HighAvailability) { # setting Fencing Agent for RH independant of customer settings as only RH is allowed if ($VMOperatingSystem -eq "RedHat") { WriteRunLog "OS is set to RedHat, HighAvailability set to FencingAgent" $HighAvailabilityAgent = "FencingAgent" } if ($VMOperatingSystem -eq "SUSE") { $_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 Header LoadHTMLHeader # Collect PowerShell Parameters $_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 $_CollectScriptParameter = CollectScriptParameters # Collect VM info $_CollectVMInfo = CollectVMInformation # Get Azure Disks assigned to VMs $_CollectVMStorage = CollectVMStorage # Get Volume Groups - CollectVMStorage needs to run first to define variables $_CollectLVMGroups = CollectLVMGroups # Get Logical Volumes - CollectVMStorage needs to run first to define variables $_CollectLVMVolumes = CollectLVMVolummes if (-not $RunLocally) { # Get ANF Volume Info $_CollectANFVolumes = CollectANFVolumes } # Get Filesystems $_CollectFileSystems = CollectFileSystems if (-not $RunLocally) { # Get Network Interfaces $_CollectNetworkInterfaces = CollectNetworkInterfaces # Get Load Balancer - CollectNetworkInterfaces needs to run first to define variables $_CollectLoadBalancer = CollectLoadBalancer } # run Quality Check $_RunQualityCheck = RunQualityCheck # Collect VM info $_CollectVMInfoAdditional = CollectVMInformationAdditional # Collect footer for support cases $_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 $_CollectLVMGroups $_CollectLVMVolumes $_CollectANFVolumes $_CollectNetworkInterfaces $_CollectLoadBalancer $_CollectVMInfoAdditional $_CollectFooter $_RunLogContent" -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>" $_HTMLReportFileName = $AzVMName + "-" + $(Get-Date -Format "yyyyMMdd-HHmm") + ".html" $_HTMLReport | Out-File .\$_HTMLReportFileName } else { # script running locally, convert result to JSON $_jsonoutput = "" | Select-Object 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 = $_jsonoutput | ConvertTo-Json Write-Host $_jsonoutput } if ((-not $RunLocally) -and $script:_ConnectVMResult) { Remove-SSHSession -SessionId $_SessionID | Out-Null } } # load report in browser for GUI runs if ($GUI) { &(".\" + $_HTMLReportFileName) } if (-not $RunLocally) { WriteRunLog -category "INFO" -message ("End " + (Get-Date)) } exit