SAPAzurePowerShellModules.psm1


$SupportedAzureVMExtensions = @(
    [pscustomobject]@{Name="MonitorX64Linux";Publisher="Microsoft.AzureCAT.AzureEnhancedMonitoring";VirtualMachineExtensionType="MonitorX64Linux"},
    [pscustomobject]@{Name="MonitorX64Windows";Publisher="Microsoft.AzureCAT.AzureEnhancedMonitoring";VirtualMachineExtensionType="MonitorX64Windows"}
) 

function Write-WithTime {
    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        [string]$Message,
              
        [string]$Level = "INFO",

        $Colour = "NONE",

        [switch] $AddNewEmptyLine
    )

    BEGIN{}
    
    PROCESS{
        try{   
           #$DateAndTime = Get-Date -Format g
           $DateAndTime = (Get-Date).ToString()

           $FormatedMessage = "[$DateAndTime] [$Level] $Message"

           if($Colour -eq "NONE"){
                Write-Host $FormatedMessage
           }elseif ($Colour -eq "Green"){
                Write-Host $FormatedMessage -ForegroundColor Green
           }

           if($AddNewEmptyLine){
               Write-Host
           }

        }
        catch{
           Write-Error  $_.Exception.Message
       }

    }

    END {}
}

function Write-WithTime_Using_WriteHost {
    <#
    .SYNOPSIS
    Formats messages includign the time stamp.
     
    .DESCRIPTION
    Formats messages includign the time stamp.
     
    .PARAMETER Message
    Specify the the text Message.
     
    .PARAMETER Level
    Specifiy severity level. Deafult is "Info". Optional parameter.
     
    .PARAMETER Colour
    Specifiy Colour of message. Deafult is "NONE". Optional parameter.
     
    .EXAMPLE
    $VMName = "myVM"
    Write-WithTime "Virtual Machine '$VMName' is alreaday running."
 #>
 
    
    [CmdletBinding()]
    param(            
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$Message,
                  
        [string] $Level = "INFO",
        
        [switch] $PrependEmptyLine,         

        [switch] $AppendEmptyLine          

        #[switch] $AddNewEmptyLine
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
            $DateAndTime = Get-Date -Format g
    
            $FormatedMessage = "[$DateAndTime] [$Level] $Message"

            if($PrependEmptyLine){
                Write-Host 
            }

            #
            switch ($Level) {
                # "INFO" { Write-Information -MessageData $FormatedMessage -InformationAction Continue }
                # "WARNING" { Write-Warning -Message $FormatedMessage }
                # "ERROR" { Write-Error -Message $FormatedMessage }
                "INFO"      { Write-Host $FormatedMessage }
                "WARN"      { Write-Host $FormatedMessage -ForegroundColor Yellow}
                "ERROR"     { Write-Host $FormatedMessage -ForegroundColor Red}
                Default {}
            }
            

            if($AppendEmptyLine){
                write-host
            }
            
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    
    }
    
    END {}
}
    

function Get-AzVMManagedDisksType {
    <#
    .SYNOPSIS
    List the disk and disk types attached to the VM.
     
    .DESCRIPTION
    List the disk and disk types attached to the VM.
     
    .PARAMETER ResourceGroupName
    Resource Group Name of the VM.
     
    .PARAMETER VMName
    VM name.
     
     
    .EXAMPLE
    # List all disk with disk type of the VM 'PR1-DB' in Azure resource group 'SAP-PR1-RG' .
    $ResourceGroupName = "SAP-PR1-RG"
    $VirtualMachineName = "PR1-DB"
    Get-AzVMManagedDisksType -ResourceGroupName $ResourceGroupName -VMName $VirtualMachineName
 #>
 
 
    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName
     
    )

    BEGIN {}
    
    PROCESS {
        try {   
            
            $obj = New-Object -TypeName psobject

            $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName

            $OSDisk = $VM.StorageProfile.OsDisk 
            $OSDiskName = $OSDisk.Name
            $OSDiskAllProperties = Get-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $OSDiskName
            $OSDiskType = $OSDiskAllProperties.Sku.Name

            $obj | add-member  -NotePropertyName "DiskName" -NotePropertyValue $OSDiskName 
            $obj | add-member  -NotePropertyName "DiskType" -NotePropertyValue $OSDiskType
            $obj | add-member  -NotePropertyName "DiskRole" -NotePropertyValue "OSDisk"
            Write-Output $obj

            $DataDisks = $VM.StorageProfile.DataDisks
            $DataDisksNames = $DataDisks.Name

            foreach ($DataDiskName in $DataDisksNames) {
                $obj = New-Object -TypeName psobject
                $DataDiskAllProperties = Get-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $DataDiskName
                $obj | add-member  -NotePropertyName "DiskName" -NotePropertyValue $DataDiskName 
                $obj | add-member  -NotePropertyName "DiskType" -NotePropertyValue $DataDiskAllProperties.Sku.Name
                $obj | add-member  -NotePropertyName "DiskRole" -NotePropertyValue "DataDisk"
                Write-Output $obj
            }

        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}

function Start-AzVMTagAndCheckVMStatus {
    <#
    .SYNOPSIS
    Starts the VM(s) with a certain tag.
     
    .DESCRIPTION
    Starts the VM(s) with a certain SAP Instance type tag.
    The expected types are:
    - SAP_ASCS
    - SAP_SCS
    - SAP_DVEBMGS
    - DBMS
    - SAP_D
    - SAP_J
     
    .PARAMETER SAPVMs
    List of VM resources. This collection is a list of VMs with same 'SAPSID' tag.
     
    .PARAMETER SAPInstanceType
    One of the SAP Instance types:
    - SAP_ASCS
    - SAP_SCS
    - SAP_DVEBMGS
    - DBMS
    - SAP_D
    - SAP_J
     
    .EXAMPLE
    # Get all the VMS with SAPSID tag 'PR1', and start ALL SAP ABAP application servers 'SAP_D'
    $SAPSID = "PR1"
    $tags = @{"SAPSystemSID"=$SAPSID}
    $SAPVMs = Get-AzResource -ResourceType "Microsoft.Compute/virtualMachines" -Tag $tags
    Start-AzVMTagAndCheckVMStatus -SAPVMs $SAPVMs -SAPInstanceType "SAP_D"
 
 #>

    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        $SAPVMs,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $SAPInstanceType        
    )

    BEGIN {}
    
    PROCESS {
        try {                                       
            $SAPInstanceSpecificVMResources = $SAPVMs | Where-Object { $_.SAPInstanceType -EQ $SAPInstanceType }                    
            #Write-Host " "

            if ($SAPInstanceSpecificVMResources -eq $null) {
                Switch ($SAPInstanceType) {
                    "SAP_ASCS" { Write-WithTime "No SAP Central Service Instance 'ASCS' VMs found in VMs Tags." }
                    "SAP_SCS" { Write-WithTime "No SAP Central Service Instance 'SCS' Instance VMs found in VMs Tags." }
                    "SAP_DVEBMGS" { Write-WithTime "No SAP ABAP Central Instance 'DVEBMGS' VM found in VMs Tags." }
                    "SAP_DBMS" { Write-WithTime "No SAP DBMS Instance VMs found in VMs Tags." }
                    "SAP_D" { Write-WithTime "No SAP SAP ABAP Application Server 'D' Instance VM found in VMs Tags." }
                    "SAP_J" { Write-WithTime "No SAP Java Application Server Instance 'J' found in VMs Tags." }   
                    Default {
                        Write-WithTime "Specified SAP Instance Type '$SAPInstanceType' is not existing."
                        Write-WithTime "Use one of these SAP instance types: 'SAP_ASCS', 'SAP_SCS', 'SAP_DVEBMGS', 'SAP_D', 'SAP_J', 'DBMS'."
                    }           
                }
            }
            else {
                Switch ($SAPInstanceType) {
                    "SAP_ASCS" { Write-WithTime   "Starting SAP Central Service Instance 'ASCS' VMs ..." }
                    "SAP_SCS" { Write-WithTime   "Starting Central Service Instance 'SCS' Instance VMs ..." }
                    "SAP_DVEBMGS" { Write-WithTime   "Starting SAP ABAP Central Instance 'DVEBMGS' VM ..." }
                    "SAP_DBMS" { Write-WithTime   "Starting SAP DBMS Instance VMs ..." }
                    "SAP_D" { Write-WithTime   "Starting SAP ABAP Application Server Instance 'D' VM ..." }
                    "SAP_J" { Write-WithTime   "Starting SAP Java Application Server Instance 'J' VMs ..." }   
                    Default {
                        Write-WithTime "Specified SAP Instance Type '$SAPInstanceType' is not existing."
                        Write-WithTime "Use one of these SAP instance types: 'SAP_ASCS', 'SAP_SCS', 'SAP_DVEBMGS', 'SAP_D', 'SAP_J', 'DBMS'."
                    }             
                }
            }


            ForEach ($VMResource in $SAPInstanceSpecificVMResources) {                
                $VMName = $VMResource.VMName
                $ResourceGroupName = $VMResource.ResourceGroupName

                $VMIsRunning = Test-AzVMIsStarted -ResourceGroupName  $ResourceGroupName -VMName $VMName

                if ($VMIsRunning -eq $False) {
                    # Start VM
                    Write-WithTime "Starting VM '$VMName' in Azure Resource Group '$ResourceGroupName' ..."
                    Start-AzVM  -ResourceGroupName $ResourceGroupName -Name $VMName -WarningAction "SilentlyContinue"

                    $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName -Status
                    $VMStatus = $VM.Statuses[1].DisplayStatus
                    
                    Write-WithTime "Virtual Machine '$VMName' status: $VMStatus"
                    
                    Start-Sleep 60   
                }
                else {
                    Write-WithTime "Virtual Machine '$VMName' is alreaday running."
                }

            }

            Write-Output " "
            
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}


function Stop-AzVMTagAndCheckVMStatus {
    <#
    .SYNOPSIS
    Stops the VM(s) with a certain tag.
     
    .DESCRIPTION
    Stops the VM(s) with a certain SAP Instance type tag.
    The expected types are:
    - SAP_ASCS
    - SAP_SCS
    - SAP_DVEBMGS
    - DBMS
    - SAP_D
    - SAP_J
     
    .PARAMETER SAPVMs
    List of VM resources. This collection is a list of VMs with same 'SAPSID' tag.
     
    .PARAMETER SAPInstanceType
    One of the SAP Instance types:
    - SAP_ASCS
    - SAP_SCS
    - SAP_DVEBMGS
    - DBMS
    - SAP_D
    - SAP_J
     
    .EXAMPLE
    # Get all the VMS with SAPSID tag 'PR1', and start ALL SAP ABAP application servers 'SAP_D'
    $SAPSID = "PR1"
    $tags = @{"SAPSystemSID"=$SAPSID}
    $SAPVMs = Get-AzResource -ResourceType "Microsoft.Compute/virtualMachines" -Tag $tags
    Stop-AzVMTagAndCheckVMStatus -SAPVMs $SAPVMs -SAPInstanceType "SAP_D"
 
 #>

    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        $SAPVMs,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $SAPInstanceType        
    )

    BEGIN {}
    
    PROCESS {
        try {   

            $SAPInstanceSpecificVMResources = $SAPVMs | Where-Object { $_.SAPInstanceType -EQ $SAPInstanceType }                    
                        
            if ($SAPInstanceSpecificVMResources -eq $null) {
                Switch ($SAPInstanceType) {
                    "SAP_ASCS" { Write-WithTime "No SAP Central Service Instance 'ASCS' VMs found in VMs Tags." }
                    "SAP_SCS" { Write-WithTime "No SAP Central Service Instance 'SCS' Instance VMs found in VMs Tags." }
                    "SAP_DVEBMGS" { Write-WithTime "No SAP ABAP Central Instance 'DVEBMGS' VM found in VMs Tags ..." }
                    "SAP_DBMS" { Write-WithTime "No SAP DBMS Instance VMs found in VMs Tags ..." }
                    "SAP_D" { Write-WithTime "No SAP SAP ABAP Application Server 'D' Instance VM found in VMs Tags." }
                    "SAP_J" { Write-WithTime "No SAP Java Application Server Instance 'J' found in VMs Tags." }   
                    Default {
                        Write-WithTime "Specified SAP Instance Type '$SAPInstanceType' is not existing."
                        Write-WithTime "Use one of these SAP instance types: 'SAP_ASCS', 'SAP_SCS', 'SAP_DVEBMGS', 'SAP_D', 'SAP_J', 'DBMS'."
                    }             
                }
            }
            else {
                Switch ($SAPInstanceType) {
                    "SAP_ASCS" { Write-WithTime   "Stopping SAP Central Service Instance 'ASCS' VMs ..." }
                    "SAP_SCS" { Write-WithTime   "Stopping SAP Central Service Instance 'SCS' VMs ..." }
                    "SAP_DVEBMGS" { Write-WithTime   "Stopping SAP ABAP Central Instance 'DVEBMG' VM ..." }
                    "SAP_DBMS" { Write-WithTime   "Stopping SAP DBMS Instance VMs ..." }
                    "SAP_D" { Write-WithTime   "Stopping SAP ABAP Application Server 'D' Instance VMs ..." }
                    "SAP_J" { Write-WithTime   "Stopping SAP Java Application Server Instance 'J' VMs ..." }   
                    Default {
                        Write-WithTime "Specified SAP Instance Type '$SAPInstanceType' is not existing."
                        Write-WithTime "Use one of these SAP instance types: 'SAP_ASCS', 'SAP_SCS', 'SAP_DVEBMGS', 'SAP_D', 'SAP_J', 'DBMS'."
                    }               
                }
            }
                      
            ForEach ($VMResource in $SAPInstanceSpecificVMResources) {                
                $VMName = $VMResource.VMName
                $ResourceGroupName = $VMResource.ResourceGroupName

                #$VMIsRunning = Test-AzVMIsStarted -ResourceGroupName $ResourceGroupName -VMName $VMName

                #if ($VMIsRunning -eq $False) {
                # Stop VM
                Write-WithTime "Stopping VM '$VMName' in Azure Resource Group '$ResourceGroupName' ..."
                Stop-AzVM  -ResourceGroupName $ResourceGroupName -Name $VMName -WarningAction "SilentlyContinue" -Force

                $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName -Status
                $VMStatus = $VM.Statuses[1].DisplayStatus
                #Write-Host ""
                Write-WithTime "Virtual Machine '$VMName' status: $VMStatus"   
                #}
                #else {
                #Write-WithTime "Virtual Machine '$VMName' is alreaday running."
                #}

            }

            #Write-Host " "
            
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}

function Get-AzSAPInstances {
    <#
    .SYNOPSIS
    Get ALL VMs with same SAPSID tag.
     
    .DESCRIPTION
    Get ALL VMs with same SAPSID tag.
    For each VM it will display:
    - SAPSID
    - Azure Resource Group Name
    - VM Name
    - SAP Instance Type
    - OS type
     
    .PARAMETER SAPSID
    SAP system SID.
     
    .EXAMPLE
    # specify SAP SID 'PR1'
    $SAPSID = "PR1"
     
    # Collect SAP VM instances with the same Tag
    $SAPInstances = Get-AzSAPInstances -SAPSID $SAPSID
 
    # List all collected instances
    $SAPInstances
 
 #>


    [CmdletBinding()]
    param(        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        $SAPSID         
    )

    BEGIN {}
    
    PROCESS {
        try {   
                      
            $tags = @{"SAPSystemSID" = $SAPSID }
            $SAPVMs = Get-AzResource -ResourceType "Microsoft.Compute/virtualMachines"  -Tag $tags

            foreach ($VMResource in $SAPVMs) {
                $obj = New-Object -TypeName psobject

                $OSType = Get-AzVMOSType -VMName $VMResource.Name -ResourceGroupName $VMResource.ResourceGroupName  
               
                $obj | add-member  -NotePropertyName "SAPSID" -NotePropertyValue $SAPSID  
                $obj | add-member  -NotePropertyName "ResourceGroupName" -NotePropertyValue $VMResource.ResourceGroupName  
                $obj | add-member  -NotePropertyName "VMName" -NotePropertyValue $VMResource.Name                  
                $obj | add-member  -NotePropertyName "SAPInstanceType"   -NotePropertyValue $VMResource.Tags.Item("SAPInstanceType")
                $obj | add-member  -NotePropertyName "OSType"   -NotePropertyValue $OSType 

                #Return formated object
                Write-Output $obj                                                
            }                       
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}
function Get-AzSAPApplicationInstances {
    <#
    .SYNOPSIS
    Get ALL VMs with same SAPSID tag, that runs applictaion layer.
     
    .DESCRIPTION
   Get ALL VMs with same SAPSID tag, that runs applictaion layer.
    For each VM it will display:
    - SAPSID
    - Azure Resource Group Name
    - VM Name
    - SAP Instance Type
    - OS type
     
    .PARAMETER SAPSID
    SAP system SID.
     
    .EXAMPLE
    # specify SAP SID 'PR1'
    $SAPSID = "PR1"
     
    # Collect SAP VM instances with the same Tag
    $SAPInstances = Get-AzSAPApplicationInstances -SAPSID $SAPSID
 
    # List all collected instances
    $SAPInstances
 
 #>


    [CmdletBinding()]
    param(        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        $SAPSID         
    )

    BEGIN {}
    
    PROCESS {
        try {   
                      
            $tags = @{"SAPSystemSID" = $SAPSID }
            $SAPVMs = Get-AzResource -ResourceType "Microsoft.Compute/virtualMachines"  -Tag $tags

            $SAPApplicationInstances = $SAPVMs | Where-Object { ($_.Tags.Item("SAPApplicationInstanceType") -EQ 'SAP_D') -or ($_.Tags.Item("SAPApplicationInstanceType") -EQ 'SAP_ASCS') -or ($_.Tags.Item("SAPApplicationInstanceType") -EQ 'SAP_SCS') -or ($_.Tags.Item("SAPApplicationInstanceType") -EQ 'SAP_DVEBMGS') }          


            foreach ($VMResource in $SAPApplicationInstances) {
                $obj = New-Object -TypeName psobject
                                
                $OSType = Get-AzVMOSType -VMName $VMResource.Name -ResourceGroupName $VMResource.ResourceGroupName  
               
                $obj | add-member  -NotePropertyName "SAPSID" -NotePropertyValue $SAPSID  
                $obj | add-member  -NotePropertyName "ResourceGroupName" -NotePropertyValue $VMResource.ResourceGroupName  
                $obj | add-member  -NotePropertyName "VMName" -NotePropertyValue $VMResource.Name                                  
                $obj | add-member  -NotePropertyName "OSType"   -NotePropertyValue $OSType 
                $obj | add-member  -NotePropertyName "SAPInstanceType"   -NotePropertyValue $VMResource.Tags.Item("SAPApplicationInstanceType") 

                #Return formated object
                Write-Output $obj
                                                
            }           
            
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}

function Test-AzVMIsStarted {
    <#
    .SYNOPSIS
    Checks if VM is started.
     
    .DESCRIPTION
    Checks if VM is started.
    If VM reachs status 'VM running', it will return $True, otherwise it will return $False
     
    .PARAMETER ResourceGroupName
    VM Azure Resource Group Name.
     
    .PARAMETER VMName
    VM Name.
     
    .EXAMPLE
    Test-AzVMIsStarted -ResourceGroupName "PR1-RG" -VMName "PR1-DB"
 #>


    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName

        
    )

    BEGIN {}
    
    PROCESS {
        try {   
            
            $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName -Status

            $VMStatus = $VMStatus = $VM.Statuses[1].DisplayStatus
                        
            if ($VMStatus -eq "VM running") {                    
                return $True                
            }
            else {
                return $False

            }
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }
    END {}
}

function Get-AzVMOSType {
    <#
    .SYNOPSIS
   Get-AzVMOSType gets the VM OS type.
     
    .DESCRIPTION
    Get-AzVMOSType gets the VM OS type, as a return value.
     
    .PARAMETER ResourceGroupName
    VM Azure Resource Group Name.
     
    .PARAMETER VMName
    VM Name.
     
    .EXAMPLE
    $OSType = Get-AzVMOSType -ResourceGroupName "PR1-RG" -VMName "PR1-DB"
 #>


    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName
        
    )

    BEGIN {}
    
    PROCESS {
        try {   
            $VM = Get-AzVM -ResourceGroupName  $ResourceGroupName -Name $VMName

            Write-Output $VM.StorageProfile.OsDisk.OsType
                        
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}


# NOT needed anymore
function Get-AzSAPHANAParametersFromTags {
    <#
    .SYNOPSIS
    Get SAP HANA parameters from DBMS VM tags.
     
    .DESCRIPTION
    Get SAP HANA parameters from DBMS VM tags. It returns an object with:[SAPHANADBSID;SAPHANAInstanceNumber,SAPHANAResourceGroupName,SAPHANAVMName]
    .PARAMETER SAPVMs
    List of SAP VMs. Get all VMs bound by SAPSID with Get-AzSAPInstances
     
    .EXAMPLE
    $SAPSID = "PR1"
    Get-AzSAPHANAParametersFromTags -SAPSID $SAPSID
 #>


    [CmdletBinding()]
    param(        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        $SAPSID        
    )

    BEGIN {}
    
    PROCESS {
        try {               
            $tags = @{"SAPSystemSID" = $SAPSID }
            $VMResources = Get-AzResource -ResourceType "Microsoft.Compute/virtualMachines"  -Tag $tags
            $HANAVMResource = $VMResources | Where-Object { $_.Tags.Item("SAPInstanceType") -EQ "SAP_DBMS" }

            $SAPHANADBSID = $HANAVMResource.Tags.Item("SAPHANADBSID")
            $SAPHANAInstanceNumber = $HANAVMResource.Tags.Item("SAPHANAInstanceNumber")
            $SAPHANAResourceGroupName = $HANAVMResource.ResourceGroupName
            $SAPHANAVMName = $HANAVMResource.Name

            $obj = New-Object -TypeName psobject
            $obj | add-member  -NotePropertyName "SAPHANADBSID" -NotePropertyValue $SAPHANADBSID
            $obj | add-member  -NotePropertyName "SAPHANAInstanceNumber" -NotePropertyValue $SAPHANAInstanceNumber
            $obj | add-member  -NotePropertyName "SAPHANAResourceGroupName" -NotePropertyValue $SAPHANAResourceGroupName
            $obj | add-member  -NotePropertyName "SAPHANAVMName" -NotePropertyValue $SAPHANAVMName
            Write-Output $obj
                        
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}

function Get-AzHANADBStatus {
    <#
    .SYNOPSIS
    Get SAP HANA DB status.
     
    .DESCRIPTION
    Get SAP HANA DB status.
 
    .PARAMETER VMName
    VM name where HANA is installed.
 
    .PARAMETER ResourceGroupName
    Resource Group Name of the HANA VM.
     
    .PARAMETER HANADBSID
    SAP HANA SID
     
    .PARAMETER HANAInstanceNumber
    SAP HANA Instance Number
 
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
     
    .EXAMPLE
    Get-AzHANADBStatus -ResourceGroupName "PR1-RG" -VMName "PR1-DB" -HANADBSID "PR1" -HANAInstanceNumber 0
 #>

    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True, HelpMessage = "HANA DB SID")] 
        [string] $HANADBSID,

        [Parameter(Mandatory = $True, HelpMessage = "HANA Instance Number")] 
        [string] $HANAInstanceNumber,

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False        

    )

    BEGIN {}
    
    PROCESS {
        try {   
            Write-WithTime "HANA DBMS '$HANADBSID' status:"

            $SAPSidUser = $HANADBSID.ToLower() + "adm"
            $SAPSIDUpper = $HANADBSID.ToUpper()
            $SAPControlPath = "/usr/sap/$SAPSIDUpper/SYS/exe/hdb/sapcontrol"            
            
            $Command = "su --login $SAPSidUser -c '$SAPControlPath -prot NI_HTTP -nr $HANAInstanceNumber -function GetSystemInstanceList'"

            if ($PrintExecutionCommand -eq $True) {
                Write-WithTime "Executing command '$Command' "
            }
                        
            $Command | Out-File "command.txt"

            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunShellScript  -ScriptPath command.txt
            $ret.Value[0].Message

            Start-Sleep 5            
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}


function Get-AzSQLServerDBStatus {
    <#
    .SYNOPSIS
    Get SQL Server DB status.
     
    .DESCRIPTION
    Get SQL Server DB status.
 
    .PARAMETER VMName
    VM name where SQL Server is installed.
 
    .PARAMETER ResourceGroupName
    Resource Group Name of the SQL Server VM.
     
    .PARAMETER DBSIDName
    SAP Database SID Name
     
    .PARAMETER DBInstanceName
    SQL Server DB Instance Name
 
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
     
    .EXAMPLE
    #Get status of default SQL Server Instance
    Get-AzSQLServerDBStatus -VMName pr1-db -ResourceGroupName gor-shared-disk-east-us -DBSIDName PR1 -PrintExecutionCommand $true
 
    .EXAMPLE
    #Get status of default SQL Server Instance
    Get-AzSQLServerDBStatus -VMName pr1-db -ResourceGroupName gor-shared-disk-east-us -DBSIDName PR1 -DBInstanceName PR1 -PrintExecutionCommand $true
 #>

    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True, HelpMessage = "SQL Server DB SID")] 
        [string] $DBSIDName,

        [Parameter(Mandatory = $False, HelpMessage = "SQL Server DB Instance Name")] 
        [string] $DBInstanceName = "",

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False        

    )

    BEGIN {}
    
    PROCESS {
        try {   
            Write-WithTime "SQL Server DBMS '$DBSIDName' status:"
            
            $Command   = "cd 'C:\Program Files\SAP\hostctrl\exe\' ; .\saphostctrl.exe -function GetDatabaseStatus -dbname $DBSIDName -dbtype mss -dbinstance $DBInstanceName"                        

            if ($PrintExecutionCommand -eq $True) {
                Write-WithTime "Executing command '$Command' "
            }
                        
            $Command | Out-File "command.txt"

            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunPowerShellScript -ScriptPath command.txt
            $ret.Value[0].Message

            Start-Sleep 10            
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}


function Get-AzDBMSStatus {
    <#
    .SYNOPSIS
    Get DB status.
     
    .DESCRIPTION
    Get DB status.
 
    .PARAMETER SAPSID
    SAP SID.
 
    .PARAMETER SAPSIDDBMSVMs
    Collection of DB VMs
         
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
     
    .EXAMPLE
    $SAPSIDDBMSVMs = Get-AzSAPDBMSInstances -SAPSID "PR2"
    Get-AzDBMSStatus -SAPSIDDBMSVMs $SAPSIDDBMSVMs
 #>

    [CmdletBinding()]
    param(                    

        [Parameter(Mandatory = $True)]         
        $SAPSIDDBMSVMs,

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False 
    )

    BEGIN {}
    
    PROCESS {
        try {   
            Switch ($SAPSIDDBMSVMs.SAPDBMSType) {

                "HANA" {                    
                    # Get SAP HANA status
                    Get-AzHANADBStatus  -ResourceGroupName $SAPSIDDBMSVMs.ResourceGroupName -VMName $SAPSIDDBMSVMs.VMName -HANADBSID $SAPSIDDBMSVMs.SAPHANASID  -HANAInstanceNumber $SAPSIDDBMSVMs.SAPHANAInstanceNumber -PrintExecutionCommand $PrintExecutionCommand                                                    
                }

                "SQLServer" {
                    # Get SQL Server status
                    Get-AzSQLServerDBStatus -ResourceGroupName $SAPSIDDBMSVMs.ResourceGroupName -VMName $SAPSIDDBMSVMs.VMName  -DBSIDName $SAPSIDDBMSVMs.SAPSID -DBInstanceName $SAPSIDDBMSVMs.DBInstanceName -PrintExecutionCommand $PrintExecutionCommand
                }

                "Sybase" {
                    # Not yet Implemented
                    Write-WithTime "Getting Sybase DBMS status is not yet implenented."
                }

                "MaxDB" {
                    # Not yet Implemented
                    Write-WithTime "Getting MaxDB DBMS status is not yet implenented."
                }

                "Oracle" {
                    # Not yet Implemented
                    Write-WithTime "Getting Oracle DBMS status is not yet implenented."
                }

                "IBMDB2" {
                    # Not yet Implemented
                    Write-WithTime "Getting IBMDB2 DBMS status is not yet implenented."
                }

                default {
                    Write-WithTime "Couldn't find any supported DBMS type. Please check on DB VM '$($SAPSIDDBMSVMs.VMName)', Tag 'SAPDBMSType'. It must have value like: 'HANA', 'SQLServer', 'Sybase', 'MaxDB' , 'Oracle', 'IBMDB2'. Current 'SAPDBMSType' Tag value is '$($SAPSIDDBMSVMs.SAPDBMSType)'"
                }
            }    
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}

function Stop-AzHANADB {
    <#
    .SYNOPSIS
    Stop SAP HANA DB.
     
    .DESCRIPTION
    Stop SAP HANA DB.
 
    .PARAMETER VMName
    VM name where HANA is installed.
 
    .PARAMETER ResourceGroupName
    Resource Group Name of the HANA VM.
     
    .PARAMETER HANADBSID
    SAP HANA SID
 
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
     
    .EXAMPLE
    Stop-AzHANADB -ResourceGroupName "PR1-RG" -VMName "PR1-DB" -HANADBSID "PR1" -SAPHANAInstanceNumber 0
 #>


    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True, HelpMessage = "HANA DB SID")] 
        [string] $HANADBSID,

        [Parameter(Mandatory=$True, HelpMessage="HANA Instance Number")] 
        [string] $SAPHANAInstanceNumber,

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False

    )

    BEGIN {}
    
    PROCESS {
        try {   
            Write-WithTime "Stopping SAP HANA DBMS '$HANADBSID' ... "

            $SAPSidUser = $HANADBSID.ToLower() + "adm"            
            $SAPSIDUpper = $HANADBSID.ToUpper()
            $SAPControlPath = "/usr/sap/$SAPSIDUpper/SYS/exe/hdb/sapcontrol"  
            
            # HDB wrapper aproach
            #$Command = "su --login $SAPSidUser -c 'HDB stop'"

            # Execute stop
            $Command = "su --login $SAPSidUser -c '$SAPControlPath -prot NI_HTTP -nr $SAPHANAInstanceNumber -function Stop 400'"

            if ($PrintExecutionCommand -eq $True) {
                Write-WithTime "Executing command '$Command' "
            }
            
            $Command | Out-File "command.txt"

            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunShellScript  -ScriptPath command.txt
            $ret.Value[0].Message

            Start-Sleep 20
            
            # Wait for 600 sec -deafult value
            $Command = "su --login $SAPSidUser -c '$SAPControlPath -prot NI_HTTP -nr $SAPHANAInstanceNumber -function WaitforStopped 600 2'"            
            
            if ($PrintExecutionCommand -eq $True) {
                Write-WithTime "Executing command '$Command' "
            }
            
            $Command | Out-File "command.txt"

            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunShellScript  -ScriptPath command.txt
            $ret.Value[0].Message

            Start-Sleep 60  

            Write-WithTime "SAP HANA DB '$HANADBSID' is stopped."          
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}

function Start-AzHANADB {
    <#
    .SYNOPSIS
    Start SAP HANA DB.
     
    .DESCRIPTION
    Start SAP HANA DB.
 
    .PARAMETER VMName
    VM name where HANA is installed.
 
    .PARAMETER ResourceGroupName
    Resource Group Name of the HANA VM.
     
    .PARAMETER SAPHANAInstanceNumber
    SAP HANA SID
 
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
     
    .EXAMPLE
    Start-AzHANADB -ResourceGroupName "PR1-RG" -VMName "PR1-DB" -HANADBSID "PR1" -SAPHANAInstanceNumber 0
 #>


    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True, HelpMessage = "HANA DB SID")] 
        [string] $HANADBSID,

        [Parameter(Mandatory=$True, HelpMessage="HANA Instance Number")] 
        [ValidateLength(1, 2)]
        [string] $SAPHANAInstanceNumber,

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False        

    )

    BEGIN {}
    
    PROCESS {
        try {   
            Write-WithTime "Starting SAP HANA DBMS '$HANADBSID' ... "

            $SAPSidUser = $HANADBSID.ToLower() + "adm"
            
            $SAPSIDUpper = $HANADBSID.ToUpper()
            $SAPControlPath = "/usr/sap/$SAPSIDUpper/SYS/exe/hdb/sapcontrol"            
            
            $Command = "su --login $SAPSidUser -c '$SAPControlPath -prot NI_HTTP -nr $SAPHANAInstanceNumber -function StartWait 2700 2'"

            #$Command = "su --login $SAPSidUser -c 'HDB start'"
            
            if ($PrintExecutionCommand -eq $True) {
                Write-WithTime "Executing command '$Command' "
            }
            
            $Command | Out-File "command.txt"

            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunShellScript  -ScriptPath command.txt
            $ret.Value[0].Message

            Start-Sleep 20            
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}

function Start-AzSQLServerDB {
    <#
    .SYNOPSIS
    Start SQL Server DB status.
     
    .DESCRIPTION
    Get SQL Server DB status.
 
    .PARAMETER VMName
    VM name where SQL Server is installed.
 
    .PARAMETER ResourceGroupName
    Resource Group Name of the SQL Server VM.
     
    .PARAMETER DBSIDName
    SAP Database SID Name
     
    .PARAMETER DBInstanceName
    SQL Server DB Instance Name
 
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
     
    .EXAMPLE
    # Start default SQL Server Instance
    Start-AzSQLServerDB -VMName pr1-db -ResourceGroupName gor-shared-disk-east-us -DBSIDName PR1 -DBInstanceName -PrintExecutionCommand $true
 
    .EXAMPLE
    # Start named SQL Server Instance
    Start-AzSQLServerDB -VMName pr1-db -ResourceGroupName gor-shared-disk-east-us -DBSIDName PR1 -DBInstanceName PR1 -PrintExecutionCommand $true
 #>

    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True, HelpMessage = "SQL Server DB SID")] 
        [string] $DBSIDName,

        [Parameter(Mandatory = $False, HelpMessage = "SQL Server DB Instance Name")] 
        [string] $DBInstanceName = "",

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False        

    )

    BEGIN {}
    
    PROCESS {
        try {   
            Write-WithTime "Starting SQL Server DBMS '$DBSIDName' ..."
            
            $Command   = "cd 'C:\Program Files\SAP\hostctrl\exe\' ; .\saphostctrl.exe -function StartDatabase -dbname $DBSIDName -dbtype mss -dbinstance $DBInstanceName"                        

            if ($PrintExecutionCommand -eq $True) {
                Write-WithTime "Executing command '$Command' "
            }
                        
            $Command | Out-File "command.txt"

            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunPowerShellScript -ScriptPath command.txt
            $ret.Value[0].Message

            Start-Sleep 10            
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}


function Stop-AzSQLServerDB {
    <#
    .SYNOPSIS
    Stop SQL Server DB status.
     
    .DESCRIPTION
    Stop SQL Server DB status.
 
    .PARAMETER VMName
    VM name where SQL Server is installed.
 
    .PARAMETER ResourceGroupName
    Resource Group Name of the SQL Server VM.
     
    .PARAMETER DBSIDName
    SAP Database SID Name
 
    .PARAMETER DBInstanceName
    SQL Server DB Instance Name
 
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
     
    .EXAMPLE
    # Stop default SQL Server Instance
    Stop-AzSQLServerDB -VMName pr1-db -ResourceGroupName gor-shared-disk-east-us -DBSIDName PR1 -DBInstanceName
 
    .EXAMPLE
    # Stop default SQL Server Instance
    Stop-AzSQLServerDB -VMName pr1-db -ResourceGroupName gor-shared-disk-east-us -DBSIDName PR1 -DBInstanceName PR1
 #>

    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True, HelpMessage = "SQL Server DB SID")] 
        [string] $DBSIDName,

        [Parameter(Mandatory = $False, HelpMessage = "SQL Server DB Instance Name")] 
        [string] $DBInstanceName = "",

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False        

    )

    BEGIN {}
    
    PROCESS {
        try {   
            Write-WithTime "Stopping SQL Server DBMS '$DBSIDName' ... :"
            
            $Command   = "cd 'C:\Program Files\SAP\hostctrl\exe\' ; .\saphostctrl.exe -function StopDatabase -dbname $DBSIDName -dbtype mss -dbinstance $DBInstanceName"                        

            if ($PrintExecutionCommand -eq $True) {
                Write-WithTime "Executing command '$Command' "
            }
                        
            $Command | Out-File "command.txt"

            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunPowerShellScript -ScriptPath command.txt
            $ret.Value[0].Message

            Start-Sleep 10            
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}





function Start-AzDBMS {
    <#
    .SYNOPSIS
    Start DBMS.
     
    .DESCRIPTION
    Start DBMS.
 
    .PARAMETER SAPSID
    SAP SID.
 
    .PARAMETER DatabaseType
    Database Type. Allowed values are: "HANA","SQLServer","MaxDB","Sybase","Oracle","IBMDB2"
 
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
     
    .EXAMPLE
    Start-AzDBMS -SAPSID "PR1" -DatabaseType "HANA"
 #>

    [CmdletBinding()]
    param(        
        [Parameter(Mandatory = $True)]         
        $SAPSIDDBMSVMs,

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False 
    )

    BEGIN {}
    
    PROCESS {
        try {   
            Switch ($SAPSIDDBMSVMs.SAPDBMSType) {

                "HANA" {
                    # Start SAP HANA DB
                    Start-AzHANADB -ResourceGroupName $SAPSIDDBMSVMs.ResourceGroupName -VMName $SAPSIDDBMSVMs.VMName  -HANADBSID $SAPSIDDBMSVMs.SAPHANASID  -SAPHANAInstanceNumber $SAPSIDDBMSVMs.SAPHANAInstanceNumber -PrintExecutionCommand $PrintExecutionCommand
                }

                "SQLServer" {                    
                    # Start SQL Server DB
                    Start-AzSQLServerDB  -ResourceGroupName $SAPSIDDBMSVMs.ResourceGroupName -VMName $SAPSIDDBMSVMs.VMName  -DBSIDName $SAPSIDDBMSVMs.SAPSID -DBInstanceName $SAPSIDDBMSVMs.DBInstanceName  -PrintExecutionCommand $PrintExecutionCommand
                }

                "Sybase" {
                    # Not yet Implemented
                    Write-WithTime "Start of SAP Sybase is not yet implemented. Relying on automatic SAP Sybase start."
                    write-output ""
                    Write-WithTime "Waiting for 3 min for DBMS auto start."
                    Start-Sleep 180
                }

                "MaxDB" {
                    # Not yet Implemented
                    Write-WithTime "Start of SAP MaxDB is not yet implemented. Relying on automatic SAP MaxDB start."
                    write-output ""
                    Write-WithTime "Waiting for 3 min for DBMS auto start."
                    Start-Sleep 180
                }

                "Oracle" {
                    # Not yet Implemented
                    Write-WithTime "Start of Oracle is not yet implemented. Relying on automatic Oracle start."
                    write-output ""
                    Write-WithTime "Waiting for 3 min for DBMS auto start."
                    Start-Sleep 180
                }

                "IBMDB2" {
                    # Not yet Implemented
                    Write-WithTime "Start of IBM DB2 is not yet implemented. Relying on automatic IBM DB2 start."
                    write-output ""
                    Write-WithTime "Waiting for 3 min for DBMS autostart."
                    Start-Sleep 180
                }
            }    
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}

function Stop-AzDBMS {
    <#
    .SYNOPSIS
    Start DBMS.
     
    .DESCRIPTION
    Start DBMS.
 
    .PARAMETER SAPSID
    SAP SID.
 
    .PARAMETER DatabaseType
    Database Type. Allowed values are: "HANA","SQLServer","MaxDB","Sybase","Oracle","IBMDB2"
 
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
     
    .EXAMPLE
    Stop-AzDBMS -SAPSID "PR1" -DatabaseType "HANA"
 #>

    [CmdletBinding()]
    param(        
        [Parameter(Mandatory = $True)]         
        $SAPSIDDBMSVMs,

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False 

    )

    BEGIN {}
    
    PROCESS {
        try {   
            Switch ($SAPSIDDBMSVMs.SAPDBMSType) {

                "HANA" {                                        
                    # Stop SAP HANA DBMS
                    Stop-AzHANADB -ResourceGroupName $SAPSIDDBMSVMs.ResourceGroupName -VMName $SAPSIDDBMSVMs.VMName -HANADBSID $SAPSIDDBMSVMs.SAPHANASID -SAPHANAInstanceNumber $SAPSIDDBMSVMs.SAPHANAInstanceNumber -PrintExecutionCommand $PrintExecutionCommand
                }

                "SQLServer" {
                    # Start SQL Server DB
                    Stop-AzSQLServerDB  -ResourceGroupName $SAPSIDDBMSVMs.ResourceGroupName -VMName $SAPSIDDBMSVMs.VMName  -DBSIDName $SAPSIDDBMSVMs.SAPSID  -DBInstanceName $SAPSIDDBMSVMs.DBInstanceName  -PrintExecutionCommand $PrintExecutionCommand
                }

                "Sybase" {
                    # Not yet Implemented
                    Write-WithTime "Stop of SAP Sybase is not yet implemented."                     
                }

                "MaxDB" {
                    # Not yet Implemented
                    Write-WithTime "Stop of SAP MaxDB is not yet implemented."                    
                }

                "Oracle" {
                    # Not yet Implemented
                    Write-WithTime "Stop of Oracle is not yet implemented."                    
                }

                "IBMDB2" {
                    # Not yet Implemented
                    Write-WithTime "Stop of IBM DB2 is not yet implemented."                    
                }
            }    
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}

function Get-AzONESAPApplicationInstance {
    <#
    .SYNOPSIS
    Get one SAPSID Instance.
     
    .DESCRIPTION
    Get one SAPSID Instance. Returned object has [VMName;SAPInstanceNumber;SAPInstanceType]
         
    .PARAMETER SAPSID
    SAP SID
     
    .EXAMPLE
    Get-AzONESAPApplicationInstance -SAPSID "PR1"
 #>


    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]         
        $SAPSIDApplicationVMs,

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False          
    )

    BEGIN {}
    
    PROCESS {
        try {                         
            $SAPSID = $SAPSIDApplicationVMs[0].SAPSID
            $VMName = $SAPSIDApplicationVMs[0].VMName
            $ResourceGroupName = $SAPSIDApplicationVMs[0].ResourceGroupName
            $SAPApplicationInstanceNumber = $SAPSIDApplicationVMs[0].SAPApplicationInstanceNumber
            $SAPInstanceType = $SAPSIDApplicationVMs[0].SAPInstanceType
            $OSType = Get-AzVMOSType -VMName $VMName -ResourceGroupName $ResourceGroupName

            if ($OSType -eq "Windows") {                
                #$SAPSIDPassword = Get-AzVMTagValue -ResourceGroupName $ResourceGroupName -VMName $VMName -KeyName "SAPSIDPassword"
                $SIDADMUser = $SAPSID.Trim().ToLower() + "adm"
                $SAPSIDCredentials = Get-AzAutomationSAPPSCredential -CredentialName  $SIDADMUser  
                $SAPSIDPassword = $SAPSIDCredentials.Password
                $PathToSAPControl = Get-AzVMTagValue -ResourceGroupName $ResourceGroupName  -VMName $VMName  -KeyName "PathToSAPControl"  
            }

            $obj = New-Object -TypeName psobject

            $obj | add-member  -NotePropertyName "SAPSID"                       -NotePropertyValue $SAPSID  
            $obj | add-member  -NotePropertyName "VMName"                       -NotePropertyValue $VMName  
            $obj | add-member  -NotePropertyName "ResourceGroupName"            -NotePropertyValue $ResourceGroupName  
            $obj | add-member  -NotePropertyName "SAPApplicationInstanceNumber" -NotePropertyValue $SAPApplicationInstanceNumber
            $obj | add-member  -NotePropertyName "SAPInstanceType"              -NotePropertyValue $SAPInstanceType
            $obj | add-member  -NotePropertyName "OSType"                       -NotePropertyValue $OSType 

            if ($OSType -eq "Windows") {
                $obj | add-member  -NotePropertyName "SAPSIDPassword"           -NotePropertyValue $SAPSIDPassword
                $obj | add-member  -NotePropertyName "PathToSAPControl"         -NotePropertyValue $PathToSAPControl               
            }

            #Return formated object
            Write-Output $obj                                    
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}


function Get-AzSAPSystemStatusLinux {
    <#
    .SYNOPSIS
    Get SAP System Status on Linux.
     
    .DESCRIPTION
    Get SAP System Status on Linux.
 
    .PARAMETER VMName
    VM name where SAP instance is installed.
 
    .PARAMETER ResourceGroupName
    Resource Group Name of the SAP instance VM.
 
    .PARAMETER InstanceNumberToConnect
    SAP Instance Number to Connect
 
    .PARAMETER SAPSID
    SAP SID
 
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
     
    .EXAMPLE
    Get-AzSAPSystemStatusLinux -ResourceGroupName "PR1-RG" -VMName "PR1-DB" -SAPSID "PR1" -InstanceNumberToConnect 1
 #>


    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True)]
        [ValidateLength(1, 2)]
        [string] $InstanceNumberToConnect,

        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False,

        [switch] $PrintOutputWithWriteHost
       
    )

    BEGIN {}
    
    PROCESS {
        try {   
            
            if ($PrintOutputWithWriteHost) {                
                Write-WithTime_Using_WriteHost "SAP System '$SAPSID' Status:"
            }else {                
                Write-WithTime "SAP System '$SAPSID' Status:"
            }                        

            $SAPSidUser = $SAPSID.ToLower() + "adm"            
            $Command = "su --login $SAPSidUser -c 'sapcontrol -nr $InstanceNumberToConnect -function GetSystemInstanceList'"
            
            if ($PrintExecutionCommand -eq $True) {

                if ($PrintOutputWithWriteHost) {                
                    Write-WithTime_Using_WriteHost "Executing command '$Command'"
                }else {                
                    Write-WithTime "Executing command '$Command'"
                }                
            }

            $Command | Out-File "command.txt"

            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunShellScript  -ScriptPath command.txt
            $ret.Value[0].Message

            #Write-Host "Waiting for 10 sec ..."
            Start-Sleep 10            
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}
function Get-AzSAPSystemStatusWindows {
    <#
    .SYNOPSIS
    Get SAP System Status on Windows.
     
    .DESCRIPTION
    Get SAP System Status on Windows.
 
    .PARAMETER VMName
    VM name where SAP instance is installed.
 
    .PARAMETER ResourceGroupName
    Resource Group Name of the SAP instance VM.
 
    .PARAMETER InstanceNumberToConnect
    SAP Instance Number to Connect
 
    .PARAMETER PathToSAPControl
    Full path to SAP Control executable.
 
    .PARAMETER SAPSID
    SAP SID
 
    .PARAMETER SAPSidPwd
    SAP <sid>adm user password
 
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
     
    .EXAMPLE
    Get-AzSAPSystemStatusWindows -ResourceGroupName "PR1-RG" -VMName "PR1-DB" -SAPSID "PR1" -InstanceNumberToConnect 1 -PathToSAPControl "C:\usr\sap\PR2\ASCS00\exe\sapcontrol.exe" -SAPSidPwd "MyPassword12"
 #>


    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True)]
        [ValidateLength(1, 2)] 
        [string] $InstanceNumberToConnect,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $PathToSAPControl,

        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $SAPSidPwd,        

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False,
        
        [switch] $PrintOutputWithWriteHost
    )

    BEGIN {}
    
    PROCESS {
        try {   
            $SAPSidUser = $SAPSID.ToLower() + "adm"            

            if ($PrintOutputWithWriteHost) {                
                Write-WithTime_Using_WriteHost "SAP System '$SAPSID' Status:"
            }else {                
                Write-WithTime "SAP System '$SAPSID' Status:"
            }
            
            $Command        = "$PathToSAPControl -nr $InstanceNumberToConnect -user $SAPSidUser '$SAPSidPwd' -function GetSystemInstanceList"
            $CommandToPrint = "$PathToSAPControl -nr $InstanceNumberToConnect -user $SAPSidUser '***pwd***' -function GetSystemInstanceList"
            
            if ($PrintExecutionCommand -eq $True) {
                if ($PrintOutputWithWriteHost) {                
                    Write-WithTime_Using_WriteHost "Executing command '$CommandToPrint' "
                }else {                
                    Write-WithTime "Executing command '$CommandToPrint' "
                }                
            }

            $Command | Out-File "command.txt"

            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunPowerShellScript -ScriptPath command.txt
            $ret.Value[0].Message

            # Waiting for 10 sec ...
            Start-Sleep 10            
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}

function Get-AzSAPSystemStatus {
    <#
    .SYNOPSIS
    Get SAP System Status.
     
    .DESCRIPTION
    Get SAP System Status. Module will automaticaly recognize Windows or Linux OS.
     
    .PARAMETER SAPSID
    SAP SID
 
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
 
    .EXAMPLE
    $SAPSIDApplicationVMs = Get-AzSAPApplicationInstances -SAPSID "SP1"
    Get-AzSAPSystemStatus -SAPSIDApplicationVMs $SAPSIDApplicationVMs
 #>


    [CmdletBinding()]
    param(       
        [Parameter(Mandatory = $True)]         
        $SAPSIDApplicationVMs,

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False   
    )

    BEGIN {}
    
    PROCESS {
        try {                       
            $ONESAPInstance = Get-AzONESAPApplicationInstance -SAPSIDApplicationVMs $SAPSIDApplicationVMs           

            if ($ONESAPInstance.OSType -eq "Linux") {
                Get-AzSAPSystemStatusLinux  -ResourceGroupName $ONESAPInstance.ResourceGroupName -VMName $ONESAPInstance.VMName -InstanceNumberToConnect $ONESAPInstance.SAPApplicationInstanceNumber -SAPSID $ONESAPInstance.SAPSID -PrintExecutionCommand $PrintExecutionCommand                            
            }
            elseif ($ONESAPInstance.OSType -eq "Windows") {
                Get-AzSAPSystemStatusWindows  -ResourceGroupName $ONESAPInstance.ResourceGroupName -VMName $ONESAPInstance.VMName -InstanceNumberToConnect $ONESAPInstance.SAPApplicationInstanceNumber -SAPSID $ONESAPInstance.SAPSID -PathToSAPControl $ONESAPInstance.PathToSAPControl -SAPSidPwd  $ONESAPInstance.SAPSIDPassword   -PrintExecutionCommand $PrintExecutionCommand

            }           
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}


function Start-AzSAPSystemLinux {
    <#
    .SYNOPSIS
    Start SAP System on Linux.
     
    .DESCRIPTION
    Start SAP System on Linux.
 
    .PARAMETER VMName
    VM name where SAP instance is installed.
 
    .PARAMETER ResourceGroupName
    Resource Group Name of the SAP instance VM.
 
    .PARAMETER InstanceNumberToConnect
    SAP Instance Number to Connect
 
    .PARAMETER SAPSID
    SAP SID
 
    .PARAMETER WaitForStartTimeInSeconds
    Number of seconds to wait for SAP system to start.
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
     
    .EXAMPLE
    Start-AzSAPSystemLinux -ResourceGroupName "PR1-RG" -VMName "PR1-DB" -SAPSID "PR1" -InstanceNumberToConnect 1
 #>


    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True)]
        [ValidateLength(1, 2)]
        [string] $InstanceNumberToConnect,

        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,

        [Parameter(Mandatory = $False)] 
        [int] $WaitForStartTimeInSeconds = 600,

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False
    )

    BEGIN {}
    
    PROCESS {
        try {   
            $SAPSidUser = $SAPSID.ToLower() + "adm"
            #$SAPSIDUpper = $SAPSID.ToUpper()
            
            Write-WithTime "Starting SAP '$SAPSID' System ..."

            $Command = "su --login $SAPSidUser -c 'sapcontrol -nr $InstanceNumberToConnect -function StartSystem'"
            
            if ($PrintExecutionCommand -eq $True) {
                Write-WithTime "Executing command '$Command' "
            }

            $Command | Out-File "command.txt"

            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunShellScript  -ScriptPath command.txt

            $ret.Value[0].Message

            Write-Output " "
            Write-WithTime "Waiting $WaitForStartTimeInSeconds seconds for SAP system '$SAPSID' to start ..."
            Start-Sleep $WaitForStartTimeInSeconds            
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}


function Start-AzSAPSystemWindows {
    <#
    .SYNOPSIS
    Get SAP System Status on Windows.
     
    .DESCRIPTION
    Get SAP System Status on Windows.
 
    .PARAMETER VMName
    VM name where SAP instance is installed.
 
    .PARAMETER ResourceGroupName
    Resource Group Name of the SAP instance VM.
 
    .PARAMETER InstanceNumberToConnect
    SAP Instance Number to Connect
 
    .PARAMETER PathToSAPControl
    Full path to SAP Control executable.
 
    .PARAMETER SAPSID
    SAP SID
 
    .PARAMETER SAPSidPwd
    SAP <sid>adm user password
 
    .PARAMETER WaitForStartTimeInSeconds
    Number of seconds to wait for SAP system to start.
 
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
     
    .EXAMPLE
    Start-AzSAPSystemWindows -ResourceGroupName "PR1-RG" -VMName "PR1-DB" -SAPSID "PR1" -InstanceNumberToConnect 1 -PathToSAPControl "C:\usr\sap\PR2\ASCS00\exe\sapcontrol.exe" -SAPSidPwd "MyPassword12"
 #>


    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True)]
        [ValidateLength(1, 2)]
        [string] $InstanceNumberToConnect,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $PathToSAPControl,

        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $SAPSidPwd,     
        
        [Parameter(Mandatory = $False)] 
        [int] $WaitForStartTimeInSeconds = 600,

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False
       
    )

    BEGIN {}
    
    PROCESS {
        try {   
            $SAPSidUser = $SAPSID.ToLower() + "adm"            

            Write-WithTime "Starting SAP '$SAPSID' System ..."           
            $Command        = "$PathToSAPControl -nr $InstanceNumberToConnect -user $SAPSidUser '$SAPSidPwd' -function StartSystem"
            $CommandToPrint = "$PathToSAPControl -nr $InstanceNumberToConnect -user $SAPSidUser '***pwd***' -function StartSystem"
            
            if ($PrintExecutionCommand -eq $True) {
                Write-WithTime "Executing command '$CommandToPrint' "
            }

            $Command | Out-File "command.txt"

            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunPowerShellScript -ScriptPath command.txt
            $ret.Value[0].Message

            Write-Output " "
            Write-WithTime "Waiting $WaitForStartTimeInSeconds seconds for SAP system '$SAPSID' to start ..."
            Start-Sleep $WaitForStartTimeInSeconds    
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}

function Start-AzSAPSystem {
    <#
    .SYNOPSIS
    Start SAP System.
     
    .DESCRIPTION
    Start SAP System. Module will automaticaly recognize Windows or Linux OS.
     
    .PARAMETER SAPSID
    SAP SID
 
    .PARAMETER WaitForStartTimeInSeconds
    Number of seconds to wait for SAP system to start.
 
    .EXAMPLE
    Start-AzSAPSystem -SAPSID "PR1"
 #>


    [CmdletBinding()]
    param(       
        [Parameter(Mandatory = $True)]         
        $SAPSIDApplicationVMs,

        [Parameter(Mandatory = $False)] 
        [int] $WaitForStartTimeInSeconds = 600,

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False        
    )

    BEGIN {}
    
    PROCESS {
        try {   
            # get one / any SAP instance
            $ONESAPInstance = Get-AzONESAPApplicationInstance -SAPSIDApplicationVMs $SAPSIDApplicationVMs

            if ($ONESAPInstance.OSType -eq "Linux") {                  
                Start-AzSAPSystemLinux    -ResourceGroupName $ONESAPInstance.ResourceGroupName -VMName $ONESAPInstance.VMName -InstanceNumberToConnect $ONESAPInstance.SAPApplicationInstanceNumber -SAPSID $ONESAPInstance.SAPSID -WaitForStartTimeInSeconds $WaitForStartTimeInSeconds -PrintExecutionCommand $PrintExecutionCommand                            
            }
            elseif ($ONESAPInstance.OSType -eq "Windows") {                
                Start-AzSAPSystemWindows  -ResourceGroupName $ONESAPInstance.ResourceGroupName -VMName $ONESAPInstance.VMName -InstanceNumberToConnect $ONESAPInstance.SAPApplicationInstanceNumber -SAPSID $ONESAPInstance.SAPSID -WaitForStartTimeInSeconds $WaitForStartTimeInSeconds -PathToSAPControl $ONESAPInstance.PathToSAPControl -SAPSidPwd  $ONESAPInstance.SAPSIDPassword  -PrintExecutionCommand $PrintExecutionCommand
            }           
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}

function Stop-AzSAPSystemLinux {
    <#
    .SYNOPSIS
    Stop SAP System on Linux.
     
    .DESCRIPTION
    Stop SAP System on Linux.
 
    .PARAMETER VMName
    VM name where SAP instance is installed.
 
    .PARAMETER ResourceGroupName
    Resource Group Name of the SAP instance VM.
 
    .PARAMETER InstanceNumberToConnect
    SAP Instance Number to Connect
 
    .PARAMETER SAPSID
    SAP SID
 
    .PARAMETER SoftShutdownTimeInSeconds
    Soft shutdown time for SAP system to stop.
 
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
     
    .EXAMPLE
    Stop-AzSAPSystemLinux -ResourceGroupName "PR1-RG" -VMName "PR1-DB" -SAPSID "PR1" -InstanceNumberToConnect 1
 #>



    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True)]
        [ValidateLength(1, 2)]
        [string] $InstanceNumberToConnect,

        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,

        [Parameter(Mandatory = $False)] 
        [int] $SoftShutdownTimeInSeconds = "600",

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False

    )

    BEGIN {}
    
    PROCESS {
        try {   
            $SAPSidUser = $SAPSID.ToLower() + "adm"            

            # Stop SAP ABAP Application Server
            Write-WithTime "Stopping SAP '$SAPSID' System ..."

            $Command = "su --login $SAPSidUser -c 'sapcontrol -nr $InstanceNumberToConnect -function StopSystem ALL $SoftShutdownTimeInSeconds $SoftShutdownTimeInSeconds'"
            
            if ($PrintExecutionCommand -eq $True) {
                Write-WithTime "Executing command '$Command' "
            }

            $Command | Out-File "command.txt"

            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunShellScript  -ScriptPath command.txt

            $ret.Value[0].Message            

            Write-Output " "
            Write-WithTime "Waiting $SoftShutdownTimeInSeconds seconds for SAP system '$SAPSID' to stop ..."
            Start-Sleep ($SoftShutdownTimeInSeconds + 30)
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}

function Stop-AzSAPSystemWindows {
    <#
        .SYNOPSIS
        Stop SAP System on Windows.
         
        .DESCRIPTION
        Stop SAP System Windows.
     
        .PARAMETER VMName
        VM name where SAP instance is installed.
     
        .PARAMETER ResourceGroupName
        Resource Group Name of the SAP instance VM.
     
        .PARAMETER InstanceNumberToConnect
        SAP Instance Number to Connect
     
        .PARAMETER PathToSAPControl
        Full path to SAP Control executable.
     
        .PARAMETER SAPSID
        SAP SID
     
        .PARAMETER SAPSidPwd
        SAP <sid>adm user password
     
        .PARAMETER SoftShutdownTimeInSeconds
        Soft shutdown time for SAP system to stop.
     
        .PARAMETER PrintExecutionCommand
        If set to $True, it will print execution command.
         
        .EXAMPLE
        Stop-AzSAPSystemWindows -ResourceGroupName "PR1-RG" -VMName "PR1-DB" -SAPSID "PR1" -InstanceNumberToConnect 1 -PathToSAPControl "C:\usr\sap\PR2\ASCS00\exe\sapcontrol.exe" -SAPSidPwd "MyPassword12"
     #>

    
    [CmdletBinding()]
    param(
            
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,
    
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,
    
        [Parameter(Mandatory = $True)]
        [ValidateLength(1, 2)]
        [string] $InstanceNumberToConnect,
    
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $PathToSAPControl,
    
        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,
    
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $SAPSidPwd,     
            
        [Parameter(Mandatory = $False)] 
        [int] $SoftShutdownTimeInSeconds = 600,
    
        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False
           
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
            $SAPSidUser = $SAPSID.ToLower() + "adm"            
    
            Write-WithTime "Stopping SAP '$SAPSID' System ..."           
            $Command        = "$PathToSAPControl -nr $InstanceNumberToConnect -user $SAPSidUser '$SAPSidPwd' -function StopSystem ALL $SoftShutdownTimeInSeconds $SoftShutdownTimeInSeconds"
            $CommandToPrint = "$PathToSAPControl -nr $InstanceNumberToConnect -user $SAPSidUser '***pwd****' -function StopSystem ALL $SoftShutdownTimeInSeconds $SoftShutdownTimeInSeconds"
                
            if ($PrintExecutionCommand -eq $True) {
                Write-WithTime "Executing command '$CommandToPrint' "
            }
    
            $Command | Out-File "command.txt"
    
            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunPowerShellScript -ScriptPath command.txt
            $ret.Value[0].Message
    
            Write-Output " "
            Write-WithTime "Waiting $SoftShutdownTimeInSeconds seconds for SAP system '$SAPSID' to stop ..."
            Start-Sleep ($SoftShutdownTimeInSeconds + 30)
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }
    
    END {}
}
    
function Stop-AzSAPSystem {
    <#
        .SYNOPSIS
        Stop SAP System.
         
        .DESCRIPTION
        Stop SAP System. Module will automaticaly recognize Windows or Linux OS.
         
        .PARAMETER SAPSID
        SAP SID
     
        .PARAMETER SoftShutdownTimeInSeconds
        Soft shutdown time for SAP system to stop.
     
        .EXAMPLE
        Stop-AzSAPSystem -SAPSID "PR1"
     #>

    
    [CmdletBinding()]
    param(       
        [Parameter(Mandatory = $True)]         
        $SAPSIDApplicationVMs,
    
        [Parameter(Mandatory = $False)] 
        [int] $SoftShutdownTimeInSeconds = 600,
    
        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False        
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
            # get one / any SAP instance
            $ONESAPInstance = Get-AzONESAPApplicationInstance -SAPSIDApplicationVMs $SAPSIDApplicationVMs 
                
            if ($ONESAPInstance.OSType -eq "Linux") {                
                Stop-AzSAPSystemLinux    -ResourceGroupName $ONESAPInstance.ResourceGroupName -VMName $ONESAPInstance.VMName -InstanceNumberToConnect $ONESAPInstance.SAPApplicationInstanceNumber -SAPSID $ONESAPInstance.SAPSID -SoftShutdownTimeInSeconds $SoftShutdownTimeInSeconds -PrintExecutionCommand $PrintExecutionCommand                            
            }
            elseif ($ONESAPInstance.OSType -eq "Windows") {                
                Stop-AzSAPSystemWindows  -ResourceGroupName $ONESAPInstance.ResourceGroupName -VMName $ONESAPInstance.VMName -InstanceNumberToConnect $ONESAPInstance.SAPApplicationInstanceNumber -SAPSID $ONESAPInstance.SAPSID -SoftShutdownTimeInSeconds $SoftShutdownTimeInSeconds -PathToSAPControl $ONESAPInstance.PathToSAPControl -SAPSidPwd  $ONESAPInstance.SAPSIDPassword  -PrintExecutionCommand $PrintExecutionCommand
            }           
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }
    
    END {}
}
    
function Get-AzVMTags {
    <#
    .SYNOPSIS
    Gets Key/Value pair tags objects.
     
    .DESCRIPTION
    Gets Key/Value pair tags objects.
     
    .PARAMETER ResourceGroupName
    ResourceGroupName.
     
    .PARAMETER VMName
    VMName.
 
    .EXAMPLE
    Get-AzVMTags
 
 #>

    [CmdletBinding()]
    param(        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName
    )

    BEGIN {}
    
    PROCESS {
        try {   
                      
            $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -VMName $VMName
            $Tags = $VM.Tags
            
            foreach ($Tag in $Tags.GetEnumerator()) {
                $obj = New-Object -TypeName psobject
                $obj | add-member  -NotePropertyName "Key"   -NotePropertyValue $Tag.Key  
                $obj | add-member  -NotePropertyName "Value" -NotePropertyValue $Tag.Value  
                 
                #Return formated object
                Write-Output $obj                
            }                                                                                             
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}

function Get-AzVMTagValue {
    <#
    .SYNOPSIS
    Gets Value of Key tag for specified VM.
     
    .DESCRIPTION
    Gets Value of Key tag for specified VM. If key do not exist, empty string is returned.
     
    .PARAMETER ResourceGroupName
    ResourceGroupName.
     
    .PARAMETER VMName
    VMName.
 
    .PARAMETER KeyName
    KeyName.
 
    .EXAMPLE
    Get-AzVMTagValue -ResourceGroupName "gor-linux-eastus2-2" -VMName "pr2-ascs" -KeyName "PathToSAPControl"
 
 #>

    [CmdletBinding()]
    param(        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $KeyName
    )

    BEGIN {}
    
    PROCESS {
        try {                         
            $VMTags = Get-AzVMTags -ResourceGroupName $ResourceGroupName -VMName $VMName 
            
            $TagWithSpecificKey = $VMTags | Where-Object Key -EQ $KeyName
                        
            $ValueOfTheTag = $TagWithSpecificKey.Value

            Write-Output $ValueOfTheTag                                                                                    
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}
function Get-AzSAPDBMSInstances {
    <#
       .SYNOPSIS
       Get ALL VMs with same SAPSID tag, that runs DBMS layer.
                    
       .DESCRIPTION
       Get ALL VMs with same SAPSID tag, that runs DBMS layer.
       For each VM it will display:
                   - SAPSID
                   - SAP Instance Type [DBMS]
                   - SAPDBMSType
                   - SAPHANASID (for HANA)
                   - SAPHANAInstanceNumber (for HANA)
                   - Azure Resource Group Name
                   - VM Name
                   - OS type
    
                    
                   .PARAMETER SAPSID
                   SAP system SID.
                    
                   .EXAMPLE
                   # specify SAP SID 'PR1'
                   $SAPSID = "PR1"
                    
                   # Collect SAP VM instances with the same Tag
                   $SAPDBMSInstance = Get-AzSAPDBMSInstances -SAPSID $SAPSID
                
                   # List all collected instances
                   $SAPDBMSInstance
                
                #>

               
    [CmdletBinding()]
    param(        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        $SAPSID         
    )
               
    BEGIN {}
                   
    PROCESS {
        try {   
                                     
            $tags = @{"SAPSystemSID" = $SAPSID }
            $SAPVMs = Get-AzResource -ResourceType "Microsoft.Compute/virtualMachines"  -Tag $tags
   
            foreach ($VMResource in $SAPVMs) {
                   
                $VMTags = Get-AzVMTags -ResourceGroupName $VMResource.ResourceGroupName -VMName $VMResource.Name 
   
                #Check if VM is DBMS host
                $IsDBMSHost = $false                
                
                # If 'SAPDBMSType' Tag exist, then VM is DBMS VM
                $SAPDBMSTypeTag = $VMTags | Where-Object Key -EQ "SAPDBMSType"
                if ($SAPDBMSTypeTag -ne $Null) {                    
                    $IsDBMSHost = $True
                }
   
                if ($IsDBMSHost) {
                    $obj = New-Object -TypeName psobject
                       
                    $OSType = Get-AzVMOSType -VMName $VMResource.Name -ResourceGroupName $VMResource.ResourceGroupName  
                                      
                       
                    $obj | add-member  -NotePropertyName "SAPSID" -NotePropertyValue $SAPSID  
   
                    # Get DBMS type
                    $SAPDBMSTypeTag = $VMTags | Where-Object Key -EQ "SAPDBMSType"  
                    $SAPDBMSType = $SAPDBMSTypeTag.Value
   
                    If ($SAPDBMSType -eq "HANA") {
                        # Get HANA SID
                        $SAPHANASIDTag = $VMTags | Where-Object Key -EQ "SAPHANASID"  
                        $SAPHANASID = $SAPHANASIDTag.Value
                        $obj | add-member  -NotePropertyName "SAPHANASID" -NotePropertyValue $SAPHANASID 
                           
                        # Get SAPHANAInstanceNumber
                        $SAPHANAInstanceNumberTag = $VMTags | Where-Object Key -EQ "SAPHANAInstanceNumber"  
                        $SAPHANAInstanceNumber = $SAPHANAInstanceNumberTag.Value
                        $obj | add-member  -NotePropertyName "SAPHANAInstanceNumber" -NotePropertyValue $SAPHANAInstanceNumber                         
   
                    }elseif ($SAPDBMSType -eq "SQLServer") {
                        $SQLServerInstanceNameTag = $VMTags | Where-Object Key -EQ "DBInstanceName"  
                        $SQLServerInstanceName = $SQLServerInstanceNameTag.Value
                        $obj | add-member  -NotePropertyName "DBInstanceName" -NotePropertyValue $SQLServerInstanceName                         
                    }
   
                    $obj | add-member  -NotePropertyName "SAPInstanceType" -NotePropertyValue "SAP_DBMS"  
                    $obj | add-member  -NotePropertyName "SAPDBMSType" -NotePropertyValue $SAPDBMSType                      
                    $obj | add-member  -NotePropertyName "ResourceGroupName" -NotePropertyValue $VMResource.ResourceGroupName                 
                    $obj | add-member  -NotePropertyName "VMName" -NotePropertyValue $VMResource.Name                    
                    $obj | add-member  -NotePropertyName "OSType"   -NotePropertyValue $OSType
   
                    #Return formated object
                    Write-Output $obj                
                }
            }                                                                                                                   
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }
               
    END {}
}


function Get-AzSAPHANAInstances {
    <#
       .SYNOPSIS
       Get ALL VMs with same SAPHANASID tag, that runs DBMS layer.
                    
       .DESCRIPTION
       Get ALL VMs with same SAPSID tag, that runs DBMS layer.
       For each VM it will display:
                   - SAP Instance Type [DBMS]
                   - SAPDBMSType
                   - SAPHANASID (for HANA)
                   - SAPHANAInstanceNumber (for HANA)
                   - Azure Resource Group Name
                   - VM Name
                   - OS type
    
                    
        .PARAMETER SAPHANASID
        SAP HANA SID.
                    
        .EXAMPLE
        # specify SAP HANA SID 'CE1'
        $SAPSID = "CE1"
                    
        # Collect SAP VM instances with the same Tag
        $SAPDBMSInstance = Get-AzSAPHANAInstances -SAPSID $SAPSID
                
        # List all collected instances
        $SAPDBMSInstance
                
#>

               
    [CmdletBinding()]
    param(        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        $SAPHANASID         
    )
               
    BEGIN {}
                   
    PROCESS {
        try {   
                                     
            $tags = @{"SAPHANASID" = $SAPHANASID }
            $SAPVMs = Get-AzResource -ResourceType "Microsoft.Compute/virtualMachines"  -Tag $tags
   
            foreach ($VMResource in $SAPVMs) {
                   
                $VMTags = Get-AzVMTags -ResourceGroupName $VMResource.ResourceGroupName -VMName $VMResource.Name 
                        
                $obj = New-Object -TypeName psobject
                       
                $OSType = Get-AzVMOSType -VMName $VMResource.Name -ResourceGroupName $VMResource.ResourceGroupName  
                                                                                              
                $SAPDBMSType = "HANA"
                    
                $obj | add-member  -NotePropertyName "SAPHANASID" -NotePropertyValue $SAPHANASID 
                           
                # Get SAPHANAInstanceNumber
                $SAPHANAInstanceNumberTag = $VMTags | Where-Object Key -EQ "SAPHANAInstanceNumber"  
                $SAPHANAInstanceNumber = $SAPHANAInstanceNumberTag.Value

                $obj | add-member  -NotePropertyName "SAPHANAInstanceNumber" -NotePropertyValue $SAPHANAInstanceNumber                                                
                $obj | add-member  -NotePropertyName "SAPInstanceType" -NotePropertyValue "SAP_DBMS"  
                $obj | add-member  -NotePropertyName "SAPDBMSType" -NotePropertyValue $SAPDBMSType                      
                $obj | add-member  -NotePropertyName "ResourceGroupName" -NotePropertyValue $VMResource.ResourceGroupName                 
                $obj | add-member  -NotePropertyName "VMName" -NotePropertyValue $VMResource.Name                    
                $obj | add-member  -NotePropertyName "OSType"   -NotePropertyValue $OSType
   
                #Return formated object
                Write-Output $obj                
            }                                                                                                                               
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }
               
    END {}
}
function Get-AzSAPApplicationInstances {
    <#
    .SYNOPSIS
    Get ALL VMs with same SAPSID tag, that runs application layer.
                                 
    .DESCRIPTION
    Get ALL VMs with same SAPSID tag, that runs application layer.
    For each VM it will display:
        - SAPSID
        - SAP Instance Type
        - SAP Application Instance Number
        - Azure Resource Group Name
        - VM Name
        - OS type
                                 
    .PARAMETER SAPSID
    SAP system SID.
                                 
    .EXAMPLE
    # specify SAP SID 'PR1'
    $SAPSID = "PR1"
                                 
    # Collect SAP VM instances with the same Tag
    $SAPApplicationInstances = Get-AzSAPApplicationInstances -SAPSID $SAPSID
                             
    # List all collected instances
    $SAPApplicationInstances
                             
#>

                            
    [CmdletBinding()]
    param(        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        $SAPSID         
    )
                            
    BEGIN {}
                                
    PROCESS {
        try {   
                                                  
            $tags = @{"SAPSystemSID" = $SAPSID }
            $SAPVMs = Get-AzResource -ResourceType "Microsoft.Compute/virtualMachines"  -Tag $tags
                
            foreach ($VMResource in $SAPVMs) {
                                
                $VMTags = Get-AzVMTags -ResourceGroupName $VMResource.ResourceGroupName -VMName $VMResource.Name 
                
                $SAPApplicationInstanceTypeTag = $VMTags | Where-Object Key -EQ "SAPApplicationInstanceType"
                if ($SAPApplicationInstanceTypeTag -ne $Null) {                    
                    # it is application SAP instance
                    
                    $obj = New-Object -TypeName psobject
                    
                    # Get 'SAPApplicationInstanceType'
                    $SAPApplicationInstanceType = $SAPApplicationInstanceTypeTag.Value

                    # Get 'SAPApplicationInstanceNumber'
                    $SAPApplicationInstanceNumberTag = $VMTags | Where-Object Key -EQ "SAPApplicationInstanceNumber"
                    $SAPApplicationInstanceNumber = $SAPApplicationInstanceNumberTag.Value                    

                    $OSType = Get-AzVMOSType -VMName $VMResource.Name -ResourceGroupName $VMResource.ResourceGroupName                                                         
                    #Write-Host "OSType: $OSType"
                    $obj | add-member  -NotePropertyName "SAPSID" -NotePropertyValue $SAPSID  

                    $obj | add-member  -NotePropertyName "SAPInstanceType" -NotePropertyValue $SAPApplicationInstanceType
                    $obj | add-member  -NotePropertyName "SAPApplicationInstanceNumber" -NotePropertyValue $SAPApplicationInstanceNumber

                    $obj | add-member  -NotePropertyName "ResourceGroupName" -NotePropertyValue $VMResource.ResourceGroupName                 
                    $obj | add-member  -NotePropertyName "VMName" -NotePropertyValue $VMResource.Name                    
                    $obj | add-member  -NotePropertyName "OSType"   -NotePropertyValue $OSType

                    #Return formated object
                    Write-Output $obj  
                }                                                
            }                                                                                                                   
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }
                            
    END {}
}

function Test-AzSAPSIDTagExist {
    <#
    .SYNOPSIS
    Test if Tag with 'SAPSystemSID' = '$SAPSID' exist. If not, exit.
     
    .DESCRIPTION
   Test if Tag with 'SAPSystemSID' = '$SAPSID' exist. If not, exit.
     
    .PARAMETER SAPSID
    SAP system SID.
     
    .EXAMPLE
    # specify SAP SID 'PR1'
    $SAPSID = "PR1"
     
    # test if SAPSIDSystem Tag with $SAPSID value exist
    Test-AzSAPSIDTagExist -SAPSID $SAPSID
 
    # List all collected instances
    $SAPInstances
 
 #>


    [CmdletBinding()]
    param(        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        $SAPSID         
    )

    BEGIN {}
    
    PROCESS {
        try {   
                      
            $tags = @{"SAPSystemSID" = $SAPSID }
            $SAPVMs = Get-AzResource -ResourceType "Microsoft.Compute/virtualMachines"  -Tag $tags

            if ($SAPVMs -eq $null) {
                Write-WithTime "Cannot find VMs with Tag 'SAPSystemSID' = '$SAPSID'"
                Write-WithTime "Exiting runbook."

                exit
            }
            else {
                Write-WithTime "Found VMs with Tag 'SAPSystemSID' = '$SAPSID'"
            }                                                        
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}

function Test-AzSAPHANASIDTagExist {
    <#
    .SYNOPSIS
    Test if Tag with 'SAPHANASID' = '$SAPHANASID' exist. If not, exit.
     
    .DESCRIPTION
    Test if Tag with 'SAPHANASID' = '$SAPHANASID' exist. If not, exit.
     
    .PARAMETER SAPSID
    SAP system SID.
     
    .EXAMPLE
    # specify SAP SIDHANA 'PR1'
    $SAPHANASID = "PR1"
     
    # test if SAPSIDSystem Tag with $SAPSID value exist
    Test-AzSAPHANASIDTagExist -SAPHANASID $SAPHANASID
 
 #>


    [CmdletBinding()]
    param(        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        $SAPHANASID         
    )

    BEGIN {}
    
    PROCESS {
        try {   
                      
            $tags = @{"SAPHANASID" = $SAPHANASID }
            $SAPVMs = Get-AzResource -ResourceType "Microsoft.Compute/virtualMachines"  -Tag $tags

            if ($SAPVMs -eq $null) {
                Write-WithTime "Cannot find VMs with Tag 'SAPHANASID' = '$SAPHANASID'"
                Write-WithTime "Exiting runbook."

                exit
            }
            else {
                Write-WithTime "Found VMs with Tag 'SAPHANASID' = '$SAPHANASID'"
            }                                                        
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}

function Show-AzSAPSIDVMApplicationInstances {
    <#
    .SYNOPSIS
    Print the SAP VMs.
     
    .DESCRIPTION
    Print the SAP VMs.
     
    .PARAMETER SAPVMs
    List of VM resources. This collection is a list of VMs with same 'SAPSID' tag.
     
     
    .EXAMPLE
    $SAPSID = "PR2"
    $SAPSIDApplicationVMs = Get-AzSAPApplicationInstances -SAPSID $SAPSID
    Show-AzSAPSIDVMApplicationInstances -SAPVMs $SAPSIDApplicationVMs
 #>

    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        $SAPVMs
           
    )

    BEGIN {}
    
    PROCESS {
        try {   
                                               
            ForEach ($SAPVM in $SAPVMs) {
                Write-Output "SAPSID: $($SAPVM.SAPSID)"  
                Write-Output "SAPInstanceType: $($SAPVM.SAPInstanceType)"  
                Write-Output "SAPApplicationInstanceNumber: $($SAPVM.SAPApplicationInstanceNumber)"  
                Write-Output "ResourceGroupName: $($SAPVM.ResourceGroupName)"  
                Write-Output  "VMName: $($SAPVM.VMName)"  
                Write-Output "OSType: $($SAPVM.OSType)"                
                Write-Output ""
            }
                            

            
            
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}

function Show-AzSAPSIDVMDBMSInstances {
    <#
    .SYNOPSIS
    Print the SAP VMs.
     
    .DESCRIPTION
    Print the SAP VMs.
     
    .PARAMETER SAPVMs
    List of VM resources. This collection is a list of VMs with same 'SAPSID' tag.
     
     
    .EXAMPLE
    $SAPSID = "PR2"
    $SAPSIDDBMSVMs = Get-AzSAPDBMSInstances -SAPSID $SAPSID
    Show-AzSAPSIDVMDBMSInstances -SAPVMs $SAPSIDApplicationVMs
 #>

    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        $SAPVMs
           
    )

    BEGIN {}
    
    PROCESS {
        try {                                                  
            ForEach ($SAPVM in $SAPVMs) {
                Write-Output "SAPSID: $($SAPVM.SAPSID)"  
                Write-Output "SAPInstanceType: $($SAPVM.SAPInstanceType)"  
                Write-Output "SAPDBMSType: $($SAPVM.SAPDBMSType)"  
                
                if ($SAPVM.SAPDBMSType -eq "HANA") {
                    Write-Output "SAPHANASID: $($SAPVM.SAPHANASID)"  
                    Write-Output "SAPHANAInstanceNumber: $($SAPVM.SAPHANAInstanceNumber)"  
                }

                Write-Output "ResourceGroupName: $($SAPVM.ResourceGroupName)"  
                Write-Output "VMName: $($SAPVM.VMName)"  
                Write-Output "OSType: $($SAPVM.OSType)"                
                Write-Output ""
            }                                                    
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}

function ConvertTo-AzVMManagedDisksToPremium {
    <#
        .SYNOPSIS
        Convert all disks of one VM to Premium type.
         
        .DESCRIPTION
        Convert all disks of one VM to Premium type.
         
        .PARAMETER ResourceGroupName
        VM Resource Group Name.
         
        .PARAMETER VMName
        VM Name.
         
        .EXAMPLE
        Convert-AzVMManagedDisksToPremium -ResourceGroupName "gor-linux-eastus2-2" -VMName "ts2-di1"
     #>

    
    [CmdletBinding()]
    param(
            
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
                  
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName
    
            
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
    
            Convert-AzVMManagedDisks -ResourceGroupName $ResourceGroupName -VMName $VMName -storageType "Premium_LRS"
    
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    
    }
    
    END {}
}
    
function ConvertTo-AzVMManagedDisksToStandard {
    <#
    .SYNOPSIS
    Convert all disks of one vM to Standard type.
             
    .DESCRIPTION
    Convert all disks of one vM to Standard type.
             
    .PARAMETER ResourceGroupName
    VM Resource Group Name.
             
    .PARAMETER VMName
    VM Name.
             
    .EXAMPLE
    Convert-AzVMManagedDisksToStandard -ResourceGroupName "gor-linux-eastus2-2" -VMName "ts2-di1"
#>

        
    [CmdletBinding()]
    param(
                
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
                      
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName
                
    )
        
    BEGIN {}
            
    PROCESS {
        try {   
        
            Convert-AzVMManagedDisks -ResourceGroupName $ResourceGroupName -VMName $VMName -storageType "Standard_LRS"                    
        
        }
        catch {
            Write-Error  $_.Exception.Message
        }
        
    }
        
    END {}
}
        
function Convert-AzVMCollectionManagedDisksToStandard {
    <#
        .SYNOPSIS
        Convert all disks of VMs collection to Standard type.
                 
        .DESCRIPTION
        Convert all disks of VMs collection to Standard type.
                 
        .PARAMETER SAPVMs
        VM collection.
             
        .EXAMPLE
             
        $SAPSID = "TS1"
        $SAPSIDApplicationVMs = Get-AzSAPApplicationInstances -SAPSID $SAPSID
        Convert-AzVMCollectionManagedDisksToStandard -SAPVMs $SAPSIDApplicationVMs
    #>

    
    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        $SAPVMs
    )
        
    BEGIN {}
            
    PROCESS {
        try {                               
            ForEach ($VM in $SAPVMs) {                    
                Write-WithTime "Converting all managed disks of VM '$($VM.VMName)' in Azure resource group '$($VM.ResourceGroupName)' to 'Standard_LRS' type .."
                ConvertTo-AzVMManagedDisksToStandard -ResourceGroupName  $VM.ResourceGroupName -VMName $VM.VMName 
                Write-Output ""
            }
        }
        catch {
            Write-Error  $_.Exception.Message
        }
        
    }
        
    END {}
}    
    
        
function Convert-AzVMCollectionManagedDisksToPremium {
    <#
        .SYNOPSIS
        Convert all disks of VMs collection to Premium type.
                 
        .DESCRIPTION
        Convert all disks of VMs collection to Premium type.
                 
        .PARAMETER SAPVMs
        VM collection.
             
        .EXAMPLE
        $SAPSID = "TS1"
        $SAPSIDApplicationVMs = Get-AzSAPApplicationInstances -SAPSID $SAPSID
        Convert-AzVMCollectionManagedDisksToPremium -SAPVMs $SAPSIDApplicationVMs
    #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        $SAPVMs                         
    )
            
    BEGIN {}
                
    PROCESS {
        try {                               
            ForEach ($VM in $SAPVMs) {                        
                Write-WithTime "Converting all managed disks of VM '$($VM.VMName)' in Azure resource group '$($VM.ResourceGroupName)' to 'Premium_LRS' type .."
                ConvertTo-AzVMManagedDisksToPremium -ResourceGroupName  $VM.ResourceGroupName -VMName $VM.VMName 
                Write-Output ""
            }
        }
        catch {
            Write-Error  $_.Exception.Message
        }
            
    }
            
    END {}
}    
        
function Get-AzVMCollectionManagedDiskType {
    <#
        .SYNOPSIS
        List all disks and disk type of VMs collection.
             
        .DESCRIPTION
        List all disks and disk type of VMs collection.
             
        .PARAMETER SAPVMs
        VM collection.
             
         
        .EXAMPLE
        $SAPSID = "TS1"
        $SAPSIDApplicationVMs = Get-AzSAPApplicationInstances -SAPSID $SAPSID
        Get-AzVMCollectionManagedDiskType -SAPVMs $SAPSIDApplicationVMs
    #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        $SAPVMs
                           
    )
        
    BEGIN {}
            
    PROCESS {
        try {                               
            ForEach ($VM in $SAPVMs) {                     
                $VMIsRunning = Test-AzVMIsStarted -ResourceGroupName  $VM.ResourceGroupName -VMName $VM.VMName
            
                if ($VMIsRunning -eq $True) {                    
                    # VM is runnign. Return to the main Runbook without listing the disks
                    return
                }
                    
                Write-WithTime "'$($VM.VMName)' VM in Azure resource group '$($VM.ResourceGroupName)' disks:"
                Get-AzVMManagedDiskType -ResourceGroupName  $VM.ResourceGroupName -VMName $VM.VMName 
                Write-Output ""
            }
        }
        catch {
            Write-Error  $_.Exception.Message
        }
        
    }
        
    END {}
}    
    
function Get-AzVMManagedDiskType {
    <#
            .SYNOPSIS
            List all disks and disk type of one VM.
             
            .DESCRIPTION
            List all disks and disk type of one VM.
             
            .PARAMETER ResourceGroupName
            Resource Group Name.
             
            .PARAMETER VMName
            VM Name.
     
            .EXAMPLE
            $SAPSID = "TS1"
            $SAPSIDApplicationVMs = Get-AzSAPApplicationInstances -SAPSID $SAPSID
            Get-AzVMCollectionManagedDiskType -ResourceGroupName "MyResourceGroupName" -VMName "myVM1"
         
    #>

    
    [CmdletBinding()]
    param(
            
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
                  
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName        
            
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
    
            $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName
    
            #OS Disk
            $OSDisk = $VM.StorageProfile.OsDisk 
            $OSDiskName = $OSDisk.Name
            $OSDiskAllProperties = Get-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $OSDiskName
            Write-Output "$OSDiskName [$($OSDiskAllProperties.Sku.Name)]"
              
    
            #Data Disks
            $DataDisks = $VM.StorageProfile.DataDisks
            $DataDisksNames = $DataDisks.Name
    
            ForEach ($DataDiskName in $DataDisksNames) {
                $DataDiskAllProperties = Get-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $DataDiskName
                Write-Output "$DataDiskName [$($DataDiskAllProperties.Sku.Name)]"
            }
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    
    }
    
    END {}
}



##################

function Convert-AzVMManagedDisks {
    <#
        .SYNOPSIS
        Convert all disks of one VM to Premium or Standard type.
         
        .DESCRIPTION
        Convert all disks of one vM to Standard type.
         
        .PARAMETER ResourceGroupName
        VM Resource Group Name.
         
        .PARAMETER VMName
        VM Name.
         
        .EXAMPLE
        # Convert to Premium disks
        Convert-AzVMManagedDisks -ResourceGroupName "gor-linux-eastus2-2" -VMName "ts2-di1" -storageType "Premium_LRS"
     
        .EXAMPLE
        # Convert to Standard disks
        Convert-AzVMManagedDisks -ResourceGroupName "gor-linux-eastus2-2" -VMName "ts2-di1" -storageType "Standard_LRS"
     
    #>

                
    [CmdletBinding()]
    param(
                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
                  
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,
    
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $storageType
                        
    )
                
    BEGIN {}
                    
    PROCESS {
        try {   
                
            $VMIsRunning = Test-AzVMIsStarted -ResourceGroupName  $ResourceGroupName -VMName $VMName

            if ($VMIsRunning -eq $True) {
                Write-WithTime("VM '$VMName' in resource group '$ResourceGroupName' is running. ")
                Write-WithTime("Skipping the disk conversion for the VM '$VMName' in resource group '$ResourceGroupName'. Disks cannot be converted when VM is running. ")
                
                return
            }
            

            $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName
    
            $OSDisk = $VM.StorageProfile.OsDisk 
            $OSDiskName = $OSDisk.Name
            $OSDiskAllProperties = Get-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $OSDiskName     
                
            Write-Output "Converting OS disk $OSDiskName to '$storageType' type ..."
            $OSDiskAllProperties.Sku = [Microsoft.Azure.Management.Compute.Models.DiskSku]::new($storageType)
            $OSDiskAllProperties | Update-AzDisk  > $Null                              
            Write-Output "Done!"                
    
            $DataDisks = $VM.StorageProfile.DataDisks
            $DataDisksNames = $DataDisks.Name
    
            ForEach ($DataDiskName in $DataDisksNames) {
                Write-Output "Converting data disk $DataDiskName to '$storageType' type ..."
                $DataDiskAllProperties = Get-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $DataDiskName
                $DataDiskAllProperties.Sku = [Microsoft.Azure.Management.Compute.Models.DiskSku]::new($storageType)
                $DataDiskAllProperties | Update-AzDisk  > $Null                     
                Write-Output "Done!"
            }                                        
        }
        catch {
            Write-Error  $_.Exception.Message
        }                
    }
                
    END {}
}
    
    
function Convert-AzALLSAPSystemVMsCollectionManagedDisksToPremium {
    <#
            .SYNOPSIS
            Convert all disks of ALL SAP SID VMs to Premium type.
                     
            .DESCRIPTION
            Convert all disks of ALL SAP SID VMs to Premium type.
                     
            .PARAMETER SAPSIDApplicationVMs
            Colelctions of VMs belonging to SAP apllication layer.
 
            .PARAMETER SAPSIDDBMSVMs
            Colelctions of VMs belonging to SAP DBMS layer.
                 
            .EXAMPLE
            $SAPSID = "TS1"
            $SAPSIDApplicationVMs = Get-AzSAPApplicationInstances -SAPSID $SAPSID
            $SAPSIDDBMSVMs = Get-AzSAPDBMSInstances -SAPSID $SAPSID
 
            Convert-AzALLSAPSystemVMsCollectionManagedDisksToPremium -SAPSIDApplicationVMs $SAPSIDApplicationVMs -SAPSIDDBMSVMs $SAPSIDDBMSVMs
         
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        $SAPSIDApplicationVMs,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        $SAPSIDDBMSVMs                
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
             
            Write-WithTime "SAP Application layer VMs disks:"
            Write-Output ""
            Get-AzVMCollectionManagedDiskType -SAPVMs $SAPSIDApplicationVMs

            Write-WithTime "Converting SAP Application layer VMs disks to 'Premium_LRS' ..."
            Convert-AzVMCollectionManagedDisksToPremium -SAPVMs $SAPSIDApplicationVMs
            Write-Output ""

            Write-WithTime "SAP Application layer VMs disks after conversion:"
            Write-Output ""
            Get-AzVMCollectionManagedDiskType -SAPVMs $SAPSIDApplicationVMs


            Write-WithTime "SAP DBMS layer VM(s)disks:"
            Write-Output ""
            Get-AzVMCollectionManagedDiskType -SAPVMs $SAPSIDDBMSVMs

            Write-WithTime "Converting DBMS layer VMs disks to 'Premium_LRS' ..."
            Convert-AzVMCollectionManagedDisksToPremium -SAPVMs $SAPSIDDBMSVMs
            Write-Output ""

            Write-WithTime "SAP DBMS layer VMs disks after conversion:"
            Write-Output ""
            Get-AzVMCollectionManagedDiskType -SAPVMs $SAPSIDDBMSVMs

        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function Convert-AzALLSAPVMsCollectionManagedDisksToPremium {
    <#
            .SYNOPSIS
            Convert all disks of ALL VMs to Premium type.
                     
            .DESCRIPTION
            Convert all disks of ALL VMs to Premium type.
                     
            .PARAMETER SAPSIDApplicationVMs
            Colelctions of VMs belonging to SAP apllication layer.
 
            .PARAMETER SAPSIDDBMSVMs
            Colelctions of VMs belonging to SAP DBMS layer.
                 
            .EXAMPLE
            $SAPSID = "TS1"
            $SAPVMs = Get-AzSAPApplicationInstances -SAPSID $SAPSID
 
            Convert-AzALLSAPVMsCollectionManagedDisksToPremium -SAPVMs $SAPVMs
         
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        $SAPVMs        
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
             
            Write-WithTime "VMs disks:"
            Write-Output ""
            Get-AzVMCollectionManagedDiskType -SAPVMs $SAPVMs

            Write-WithTime "Converting VMs disks to 'Premium_LRS' ..."
            Convert-AzVMCollectionManagedDisksToPremium -SAPVMs $SAPVMs
            Write-Output ""

            Write-WithTime "VMs disks after conversion:"
            Write-Output ""
            Get-AzVMCollectionManagedDiskType -SAPVMs $SAPVMs          

        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function Convert-AzALLSAPSystemVMsCollectionManagedDisksToStandard {
    <#
            .SYNOPSIS
            Convert all disks of ALL SAP SID VMs to Standard type.
                     
            .DESCRIPTION
            Convert all disks of ALL SAP SID VMs to Standard type.
                     
            .PARAMETER SAPSIDApplicationVMs
            Collrctions of VMs belonging to SAP apllication layer.
 
            .PARAMETER SAPSIDDBMSVMs
            Colelctions of VMs belonging to SAP DBMS layer.
                 
            .EXAMPLE
            $SAPSID = "TS1"
            $SAPSIDApplicationVMs = Get-AzSAPApplicationInstances -SAPSID $SAPSID
            $SAPSIDDBMSVMs = Get-AzSAPDBMSInstances -SAPSID $SAPSID
 
            Convert-AzALLSAPSystemVMsCollectionManagedDisksToStandard -SAPSIDApplicationVMs $SAPSIDApplicationVMs -SAPSIDDBMSVMs $SAPSIDDBMSVMs
         
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        $SAPSIDApplicationVMs,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        $SAPSIDDBMSVMs                
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
             
            Write-WithTime "SAP Application layer VMs disks:"
            Write-Output ""
            Get-AzVMCollectionManagedDiskType -SAPVMs $SAPSIDApplicationVMs

            Write-WithTime "Converting SAP Application layer VMs disks to 'Standard_LRS' ..."
            Convert-AzVMCollectionManagedDisksToStandard -SAPVMs $SAPSIDApplicationVMs
            Write-Output ""

            Write-WithTime "SAP Application layer VMs disks after conversion:"
            Write-Output ""
            Get-AzVMCollectionManagedDiskType -SAPVMs $SAPSIDApplicationVMs


            Write-WithTime "SAP DBMS layer VM(s)disks:"
            Write-Output ""
            Get-AzVMCollectionManagedDiskType -SAPVMs $SAPSIDDBMSVMs

            Write-WithTime "Converting DBMS layer VMs disks to 'Standard_LRS' ..."
            Convert-AzVMCollectionManagedDisksToStandard -SAPVMs $SAPSIDDBMSVMs
            Write-Output ""

            Write-WithTime "SAP DBMS layer VMs disks after conversion:"
            Write-Output ""
            Get-AzVMCollectionManagedDiskType -SAPVMs $SAPSIDDBMSVMs

        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function Convert-AzALLSAPVMsCollectionManagedDisksToStandard {
    <#
            .SYNOPSIS
            Convert all disks of VMs to Standard type.
                     
            .DESCRIPTION
            Convert all disks of VMs to Standard type.
                     
            .PARAMETER SAPSIDApplicationVMs
            Collrctions of VMs belonging to SAP apllication layer.
 
            .PARAMETER SAPVMs
            Collections of VMs .
                 
            .EXAMPLE
            $SAPSID = "TS1"
            $SAPSIDApplicationVMs = Get-AzSAPApplicationInstances -SAPSID $SAPSID
 
            Convert-AzALLSAPVMsCollectionManagedDisksToStandard -SAPVMs $SAPSIDApplicationVMs
         
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        $SAPVMs
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
             
            Write-WithTime "VMs disks:"
            Write-Output ""
            Get-AzVMCollectionManagedDiskType -SAPVMs $SAPVMs

            Write-WithTime "Converting VMs disks to 'Standard_LRS' ..."
            Convert-AzVMCollectionManagedDisksToStandard -SAPVMs $SAPVMs
            Write-Output ""

            Write-WithTime "VMs disks after conversion:"
            Write-Output ""
            Get-AzVMCollectionManagedDiskType -SAPVMs $SAPVMs            

        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function New-AzSAPSystemHANATags {
    <#
            .SYNOPSIS
            Set Tags on Standalone SAP HANA belonging to an SAP SID system.
                     
            .DESCRIPTION
            Set Tags on Standalone SAP HANA belonging to an SAP SID system.
                     
            .PARAMETER ResourceGroupName
            Resource Group Name of the VM.
     
            .PARAMETER VMName
            VM name.
 
            .PARAMETER SAPSID
            SAP system SID.
 
            .PARAMETER SAPHANASID
            SAP HANA SID.
 
            .PARAMETER SAPHANAINstanceNumber
            SAP HANA InstanceNumber.
                 
            .EXAMPLE
           # Set Tags on Standalone HANA belonging to an SAP system
           $ResourceGroupName = "gor-linux-eastus2"
           $VMName = "ts2-db"
 
           New-AzSAPSystemHANATags -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID "TS1" -SAPHANASID "TS2" -SAPHANAINstanceNumber 0
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,
        
        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,

        [Parameter(Mandatory = $True, HelpMessage = "SAP HANA <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPHANASID,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [ValidateLength(1, 2)]
        [string] $SAPHANAInstanceNumber
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                                                                               
            $SAPDBMSType = "HANA"
            
            $tags = @{"SAPSystemSID" = $SAPSID; "SAPHANASID" = $SAPHANASID; "SAPHANAINstanceNumber" = $SAPHANAInstanceNumber; "SAPDBMSType" = $SAPDBMSType; }
            
            $resource = Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceName $VMName
            #New-AzTag -ResourceId $resource.id -Tag $tags
            Update-AzTag -ResourceId $resource.id -Tag $tags -Operation Merge
        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function New-AzSAPStandaloneHANATags {
    <#
            .SYNOPSIS
            Set Tags on Standalone SAP HANA.
                     
            .DESCRIPTION
            Set Tags on Standalone SAP HANA.
                     
            .PARAMETER ResourceGroupName
            Resource Group Name of the VM.
     
            .PARAMETER VMName
            VM name.
 
            .PARAMETER SAPHANASID
            SAP HANA SID.
 
            .PARAMETER SAPHANAINstanceNumber
            SAP HANA InstanceNumber.
                 
            .EXAMPLE
           # Set Tags on Standalone HANA belonging to an SAP system
           $ResourceGroupName = "gor-linux-eastus2"
           $VMName = "ts2-db"
 
           New-AzSAPHANATags -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID "TS1" -SAPHANASID "TS2" -SAPHANAINstanceNumber 0
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,
            

        [Parameter(Mandatory = $True, HelpMessage = "SAP HANA <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPHANASID,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 2)]     
        [string] $SAPHANAInstanceNumber
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                                                                               
            $SAPDBMSType = "HANA"
            
            $tags = @{"SAPHANASID" = $SAPHANASID; "SAPHANAINstanceNumber" = $SAPHANAInstanceNumber; "SAPDBMSType" = $SAPDBMSType; }
            
            $resource = Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceName $VMName
            #New-AzTag -ResourceId $resource.id -Tag $tags
            Update-AzTag -ResourceId $resource.id -Tag $tags -Operation Merge
        }
        catch {
            Write-Error  $_.Exception.Message
        }                
    }
                
    END {}
}    

function New-AzSAPSystemHANAAndASCSTags {
    <#
            .SYNOPSIS
            Set Tags on Standalone SAP HANA with SAP 'ASCS' instance.
                     
            .DESCRIPTION
            Set Tags on Standalone SAP HANA with SAP 'ASCS' instance. This is used with SAP Central System where complete system is isntelld on one VM, or distributed system where HANA and ASCS instance are located on the same VM
                     
            .PARAMETER ResourceGroupName
            Resource Group Name of the VM.
     
            .PARAMETER VMName
            VM name.
 
            .PARAMETER SAPSID
            SAP system SID.
 
            .PARAMETER SAPHANASID
            SAP HANA SID.
 
            .PARAMETER SAPHANAINstanceNumber
            SAP HANA InstanceNumber.
                 
            .PARAMETER SAPApplicationInstanceNumber
            SAP ASCS Instance Number.
 
            .EXAMPLE
           # Set Tags on Standalone HANA belonging to an SAP system
           $ResourceGroupName = "gor-linux-eastus2"
           $VMName = "ts2-db"
 
           New-AzSAPSystemHANATags -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID "TS1" -SAPHANASID "TS2" -SAPHANAINstanceNumber 0 -SAPApplicationInstanceNumber 1
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,
        
        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,

        [Parameter(Mandatory = $True, HelpMessage = "SAP HANA <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPHANASID,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 2)]      
        [string] $SAPHANAInstanceNumber,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 2)]    
        [string] $SAPApplicationInstanceNumber
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
                                    
            #$DBMSInstance = $true
            $SAPDBMSType = "HANA"            
            $SAPApplicationInstanceType = "SAP_ASCS"
            
            $tags = @{"SAPSystemSID" = $SAPSID; "SAPHANASID" = $SAPHANASID; "SAPHANAINstanceNumber" = $SAPHANAInstanceNumber; "SAPDBMSType" = $SAPDBMSType; "SAPApplicationInstanceType" = $SAPApplicationInstanceType ; "SAPApplicationInstanceNumber" = $SAPApplicationInstanceNumber }
            
            $resource = Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceName $VMName
            #New-AzTag -ResourceId $resource.id -Tag $tags
            Update-AzTag -ResourceId $resource.id -Tag $tags -Operation Merge            
        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function New-AzSAPSystemASCSLinuxTags {
    <#
            .SYNOPSIS
            Set Tags on Standalone SAP 'ASCS' instance on Linux.
                     
            .DESCRIPTION
            Set Tags on Standalone SAP 'ASCS' instance on Linux.
                     
            .PARAMETER ResourceGroupName
            Resource Group Name of the VM.
     
            .PARAMETER VMName
            VM name.
 
            .PARAMETER SAPSID
            SAP system SID.
                 
            .PARAMETER SAPApplicationInstanceNumber
            SAP ASCS Instance Number.
 
            .EXAMPLE
           # Set Tags on Standalone HANA belonging to an SAP system
           $ResourceGroupName = "gor-linux-eastus2"
           $VMName = "ts2-db"
 
           New-AzSAPSystemASCSLinux -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID "TS1" -SAPApplicationInstanceNumber 1
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,
        
        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,
        
        [Parameter(Mandatory = $True)]
        [ValidateLength(1, 2)]
        [string] $SAPApplicationInstanceNumber
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
                                                         
            $SAPApplicationInstanceType = "SAP_ASCS"
            
            $tags = @{"SAPSystemSID" = $SAPSID; "SAPApplicationInstanceType" = $SAPApplicationInstanceType ; "SAPApplicationInstanceNumber" = $SAPApplicationInstanceNumber }
            
            $resource = Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceName $VMName
            #New-AzTag -ResourceId $resource.id -Tag $tags
            Update-AzTag -ResourceId $resource.id -Tag $tags -Operation Merge            
        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function New-AzSAPSystemSCSLinuxTags {
    <#
            .SYNOPSIS
            Set Tags on Standalone SAP 'SCS' instance on Linux.
                     
            .DESCRIPTION
            Set Tags on Standalone SAP 'SCS' instance on Linux.
                     
            .PARAMETER ResourceGroupName
            Resource Group Name of the VM.
     
            .PARAMETER VMName
            VM name.
 
            .PARAMETER SAPSID
            SAP system SID.
                 
            .PARAMETER SAPApplicationInstanceNumber
            SAP ASCS Instance Number.
 
            .EXAMPLE
           # Set Tags on Standalone HANA belonging to an SAP system
           $ResourceGroupName = "gor-linux-eastus2"
           $VMName = "ts2-db"
 
           New-AzSAPSystemSCSLinux -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID "TS1" -SAPApplicationInstanceNumber 1
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,
        
        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,
        
        [Parameter(Mandatory = $True)]
        [ValidateLength(1, 2)]
        [string] $SAPApplicationInstanceNumber
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
                                                         
            $SAPApplicationInstanceType = "SAP_SCS"
            
            $tags = @{"SAPSystemSID" = $SAPSID; "SAPApplicationInstanceType" = $SAPApplicationInstanceType ; "SAPApplicationInstanceNumber" = $SAPApplicationInstanceNumber }
            
            $resource = Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceName $VMName
            #New-AzTag -ResourceId $resource.id -Tag $tags
            Update-AzTag -ResourceId $resource.id -Tag $tags -Operation Merge            
        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function New-AzSAPSystemSAPDVEBMGSLinuxTags {
    <#
            .SYNOPSIS
            Set Tags on Standalone SAP 'DVEBMGS' instance on Linux.
                     
            .DESCRIPTION
            Set Tags on Standalone SAP 'DVEBMGS' instance on Linux.
                     
            .PARAMETER ResourceGroupName
            Resource Group Name of the VM.
     
            .PARAMETER VMName
            VM name.
 
            .PARAMETER SAPSID
            SAP system SID.
                 
            .PARAMETER SAPApplicationInstanceNumber
            SAP DVEBMGS Instance Number.
 
            .EXAMPLE
           # Set Tags on Standalone HANA belonging to an SAP system
           $ResourceGroupName = "gor-linux-eastus2"
           $VMName = "ts2-db"
 
           New-AzSAPSystemSAPDVEBMGSLinux -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID "TS1" -SAPApplicationInstanceNumber 1
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,
        
        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 2)]    
        [string] $SAPApplicationInstanceNumber
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
                                                         
            $SAPApplicationInstanceType = "SAP_DVEBMGS"
            
            $tags = @{"SAPSystemSID" = $SAPSID; "SAPApplicationInstanceType" = $SAPApplicationInstanceType ; "SAPApplicationInstanceNumber" = $SAPApplicationInstanceNumber }
            
            $resource = Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceName $VMName
            #New-AzTag -ResourceId $resource.id -Tag $tags
            Update-AzTag -ResourceId $resource.id -Tag $tags -Operation Merge            
        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function New-AzSAPSystemSAPDialogInstanceApplicationServerLinuxTags {
    <#
            .SYNOPSIS
            Set Tags on Standalone SAP Dialog 'D' Instance Application Server instance on Linux.
                     
            .DESCRIPTION
            Set Tags on Standalone SAP Dialog 'D' Instance Application Server instance on Linux.
                     
            .PARAMETER ResourceGroupName
            Resource Group Name of the VM.
     
            .PARAMETER VMName
            VM name.
 
            .PARAMETER SAPSID
            SAP system SID.
                 
            .PARAMETER SAPApplicationInstanceNumber
            SAP SAP Dialog 'D' Instance Application Server Instance Number.
 
            .EXAMPLE
           # Set Tags on Standalone HANA belonging to an SAP system
           $ResourceGroupName = "gor-linux-eastus2"
           $VMName = "ts2-db"
 
           New-AzSAPSystemSAPDialogInstanceApplicationServerLinux -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID "TS1" -SAPApplicationInstanceNumber 1
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,
        
        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 2)]
        [string] $SAPApplicationInstanceNumber
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
                                                         
            $SAPApplicationInstanceType = "SAP_D"
            
            $tags = @{"SAPSystemSID" = $SAPSID; "SAPApplicationInstanceType" = $SAPApplicationInstanceType ; "SAPApplicationInstanceNumber" = $SAPApplicationInstanceNumber }
            
            $resource = Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceName $VMName
            #New-AzTag -ResourceId $resource.id -Tag $tags
            Update-AzTag -ResourceId $resource.id -Tag $tags -Operation Merge            
        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function New-AzSAPSystemSAPJavaApplicationServerInstanceLinuxTags {
    <#
            .SYNOPSIS
            Set Tags on Standalone SAP Java 'J' Instance Application Server instance on Linux.
                     
            .DESCRIPTION
            Set Tags on Standalone SAP Java 'J' Instance Application Server instance on Linux.
                     
            .PARAMETER ResourceGroupName
            Resource Group Name of the VM.
     
            .PARAMETER VMName
            VM name.
 
            .PARAMETER SAPSID
            SAP system SID.
                 
            .PARAMETER SAPApplicationInstanceNumber
            SAP SAP Dialog 'D' Instance Application Server Instance Number.
 
            .EXAMPLE
           # Set Tags on Standalone HANA belonging to an SAP system
           $ResourceGroupName = "gor-linux-eastus2"
           $VMName = "ts2-db"
 
           New-AzSAPSystemSAPJavaApplicationServerInstanceLinuxTags -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID "TS1" -SAPApplicationInstanceNumber 1
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,
        
        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 2)]
        [string] $SAPApplicationInstanceNumber
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
                                                         
            $SAPApplicationInstanceType = "SAP_J"
            
            $tags = @{"SAPSystemSID" = $SAPSID; "SAPApplicationInstanceType" = $SAPApplicationInstanceType ; "SAPApplicationInstanceNumber" = $SAPApplicationInstanceNumber }
            
            $resource = Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceName $VMName
            #New-AzTag -ResourceId $resource.id -Tag $tags
            Update-AzTag -ResourceId $resource.id -Tag $tags -Operation Merge            
        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function New-AzSAPsidadmUserAutomationCredential {
    <#
            .SYNOPSIS
           Creates new Azure Automation credentials for SAP <sid>adm user,need on Windows OS.
                     
            .DESCRIPTION
            Creates new Azure Automation credentials for SAP <sid>adm user,need on Windows OS.
                     
            .PARAMETER AutomationAccountResourceGroupName
            Azure Automation Account Resource Group Name.
     
            .PARAMETER AutomationAccountName
            Azure Automation Account Name.
 
            .PARAMETER SAPSID
            SAP system SID.
                 
            .PARAMETER SAPsidadmUserPassword
            SAP <sidadm> user password.
 
            .EXAMPLE
           # Set Tags on Standalone HANA belonging to an SAP system
           $ResourceGroupName = "gor-linux-eastus2"
           New-AzSAPsidadmUserAutomationCredential -AutomationAccountResourceGroupName $ResourceGroupName -AutomationAccountName "MyAzureAutomationAccount" -SAPSID "TS1" -SAPsidadmUserPassword "MyPwd"
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$AutomationAccountResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $AutomationAccountName,
        
        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $SAPsidadmUserPassword
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
                    
            $User = $SAPSID.Trim().ToLower() + "adm"
            $Password = ConvertTo-SecureString $SAPsidadmUserPassword  -AsPlainText -Force
            $Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $Password
            New-AzAutomationCredential -AutomationAccountName $AutomationAccountName  -Name $user  -Value $Credential -ResourceGroupName $AutomationAccountResourceGroupName
            
        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function New-AzSAPSystemASCSWindowsTags {
    <#
            .SYNOPSIS
            Set Tags on Standalone SAP 'ASCS' instance on Windows.
                     
            .DESCRIPTION
            Set Tags on Standalone SAP 'ASCS' instance on Windows.
                     
            .PARAMETER ResourceGroupName
            Resource Group Name of the VM.
     
            .PARAMETER VMName
            VM name.
 
            .PARAMETER SAPSID
            SAP system SID.
                 
            .PARAMETER SAPApplicationInstanceNumber
            SAP ASCS Instance Number.
 
            .EXAMPLE
           # Set Tags on Standalone HANA belonging to an SAP system
           $ResourceGroupName = "gor-linux-eastus2"
           $VMName = "ts2-db"
 
           New-AzSAPSystemASCSWindowsTags -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID "TS1" -SAPApplicationInstanceNumber 1 -PathToSAPControl "S:\usr\sap\ASCS00\exe\sapcontrol.exe" -AutomationAccountResourceGroupName "RG-AutomationAccount" -AutomationAccountName "my-sap-autoamtion-account" -SAPsidadmUserPassword "MyPwd374"
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,
        
        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 2)]   
        [string] $SAPApplicationInstanceNumber,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $PathToSAPControl,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$AutomationAccountResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $AutomationAccountName,        
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $SAPsidadmUserPassword
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
                                                         
            $SAPApplicationInstanceType = "SAP_ASCS"            

            # Create VM Tags
            Write-WithTime "Creating '$SAPApplicationInstanceType' Tags on VM '$VMName' in resource group '$ResourceGroupName' ...."
            $tags = @{"SAPSystemSID" = $SAPSID; "SAPApplicationInstanceType" = $SAPApplicationInstanceType ; "SAPApplicationInstanceNumber" = $SAPApplicationInstanceNumber; "PathToSAPControl" = $PathToSAPControl }
            
            $resource = Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceName $VMName
            #New-AzTag -ResourceId $resource.id -Tag $tags
            Update-AzTag -ResourceId $resource.id -Tag $tags -Operation Merge

            # Create Credetnials in Azure Automation Secure Area
            $User = $SAPSID.Trim().ToLower() + "adm"
            Write-WithTime "Creating credentials in Azure automation account secure area for user '$User' ...."
            New-AzSAPsidadmUserAutomationCredential -AutomationAccountResourceGroupName $AutomationAccountResourceGroupName -AutomationAccountName $AutomationAccountName -SAPSID $SAPSID -SAPsidadmUserPassword $SAPsidadmUserPassword                                  
        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function New-AzSAPSystemSCSWindowsTags {
    <#
            .SYNOPSIS
            Set Tags on Standalone SAP Java 'SCS' instance on Windows.
                     
            .DESCRIPTION
            Set Tags on Standalone SAP Java 'SCS' instance on Windows.
                     
            .PARAMETER ResourceGroupName
            Resource Group Name of the VM.
     
            .PARAMETER VMName
            VM name.
 
            .PARAMETER SAPSID
            SAP system SID.
                 
            .PARAMETER SAPApplicationInstanceNumber
            SAP ASCS Instance Number.
 
            .EXAMPLE
           # Set Tags on Standalone HANA belonging to an SAP system
           $ResourceGroupName = "gor-linux-eastus2"
           $VMName = "ts2-db"
            
           New-AzSAPSystemSCSWindowsTags -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID "TS1" -SAPApplicationInstanceNumber 1 -PathToSAPControl "S:\usr\sap\TS1\SCS01\exe\sapcontrol.exe" -AutomationAccountResourceGroupName "RG-AutomationAccount" -AutomationAccountName "my-sap-autoamtion-account" -SAPsidadmUserPassword "MyPwd374"
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,
        
        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 2)]   
        [string] $SAPApplicationInstanceNumber,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $PathToSAPControl,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$AutomationAccountResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $AutomationAccountName,        
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $SAPsidadmUserPassword
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
                                                         
            $SAPApplicationInstanceType = "SAP_SCS"            

            # Create VM Tags
            Write-WithTime "Creating '$SAPApplicationInstanceType' Tags on VM '$VMName' in resource group '$ResourceGroupName' ...."
            $tags = @{"SAPSystemSID" = $SAPSID; "SAPApplicationInstanceType" = $SAPApplicationInstanceType ; "SAPApplicationInstanceNumber" = $SAPApplicationInstanceNumber; "PathToSAPControl" = $PathToSAPControl }
            
            $resource = Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceName $VMName
            #New-AzTag -ResourceId $resource.id -Tag $tags
            Update-AzTag -ResourceId $resource.id -Tag $tags -Operation Merge

            # Create Credetnials in Azure Automation Secure Area
            $User = $SAPSID.Trim().ToLower() + "adm"
            Write-WithTime "Creating credentials in Azure automation account secure area for user '$User' ...."
            New-AzSAPsidadmUserAutomationCredential -AutomationAccountResourceGroupName $AutomationAccountResourceGroupName -AutomationAccountName $AutomationAccountName -SAPSID $SAPSID -SAPsidadmUserPassword $SAPsidadmUserPassword                                  
        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    
function New-AzSAPSystemDVEBMGSWindowsTags {
    <#
            .SYNOPSIS
            Set Tags on Standalone SAP 'DVEBMGS' instance on Windows.
                     
            .DESCRIPTION
            Set Tags on Standalone SAP 'DVEBMGS' instance on Windows.
                     
            .PARAMETER ResourceGroupName
            Resource Group Name of the VM.
     
            .PARAMETER VMName
            VM name.
 
            .PARAMETER SAPSID
            SAP system SID.
                 
            .PARAMETER SAPApplicationInstanceNumber
            SAP DVEBMGS Instance Number.
 
            .EXAMPLE
           # Set Tags on Standalone HANA belonging to an SAP system
           $ResourceGroupName = "gor-linux-eastus2"
           $VMName = "ts2-db"
 
           New-AzSAPSystemDVEBMGSWindows -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID "TS1" -SAPApplicationInstanceNumber 1 -PathToSAPControl "S:\usr\sap\TS1\J01\exe\sapcontrol.exe" -AutomationAccountResourceGroupName "rg-autom-account" -AutomationAccountName "sap-automat-acc" -SAPsidadmUserPassword "MyPass789j$&"
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,
        
        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 2)]
        [string] $SAPApplicationInstanceNumber,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $PathToSAPControl,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$AutomationAccountResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $AutomationAccountName,        
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $SAPsidadmUserPassword
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
                                                         
            $SAPApplicationInstanceType = "SAP_DVEBMGS"            

            # Create VM Tags
            Write-WithTime "Creating '$SAPApplicationInstanceType' Tags on VM '$VMName' in resource group '$ResourceGroupName' ...."
            $tags = @{"SAPSystemSID" = $SAPSID; "SAPApplicationInstanceType" = $SAPApplicationInstanceType ; "SAPApplicationInstanceNumber" = $SAPApplicationInstanceNumber; "PathToSAPControl" = $PathToSAPControl }
            
            $resource = Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceName $VMName
            #New-AzTag -ResourceId $resource.id -Tag $tags
            Update-AzTag -ResourceId $resource.id -Tag $tags -Operation Merge

            # Create Credetnials in Azure Automation Secure Area
            $User = $SAPSID.Trim().ToLower() + "adm"
            Write-WithTime "Creating credentials in Azure automation account secure area for user '$User' ...."
            New-AzSAPsidadmUserAutomationCredential -AutomationAccountResourceGroupName $AutomationAccountResourceGroupName -AutomationAccountName $AutomationAccountName -SAPSID $SAPSID -SAPsidadmUserPassword $SAPsidadmUserPassword          
            
            
        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    


function New-AzSAPSystemSAPDialogInstanceApplicationServerWindowsTags {
    <#
            .SYNOPSIS
            Set Tags on Standalone Dialog Instance Application Server instance on Windows.
                     
            .DESCRIPTION
            Set Tags on Standalone Dialog Instance Application Server instance on Windows.
                     
            .PARAMETER ResourceGroupName
            Resource Group Name of the VM.
     
            .PARAMETER VMName
            VM name.
 
            .PARAMETER SAPSID
            SAP system SID.
                 
            .PARAMETER SAPApplicationInstanceNumber
            SAP 'D' Dialog Instance Application Server Instance Number.
 
            .EXAMPLE
           # Set Tags on Standalone HANA belonging to an SAP system
           $ResourceGroupName = "gor-eastus2"
           $VMName = "ts2-di0"
             
           New-AzSAPSystemSAPDialogInstanceApplicationServerWindowsTags -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID "TS1" -SAPApplicationInstanceNumber 1 -PathToSAPControl "S:\usr\sap\TS1\J01\exe\sapcontrol.exe" -AutomationAccountResourceGroupName "rg-autom-account" -AutomationAccountName "sap-automat-acc" -SAPsidadmUserPassword "MyPass789j$&"
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,
        
        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 2)]
        [string] $SAPApplicationInstanceNumber,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $PathToSAPControl,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$AutomationAccountResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $AutomationAccountName,        
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $SAPsidadmUserPassword
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
                                                         
            $SAPApplicationInstanceType = "SAP_D"            

            # Create VM Tags
            Write-WithTime "Creating '$SAPApplicationInstanceType' Tags on VM '$VMName' in resource group '$ResourceGroupName' ...."
            $tags = @{"SAPSystemSID" = $SAPSID; "SAPApplicationInstanceType" = $SAPApplicationInstanceType ; "SAPApplicationInstanceNumber" = $SAPApplicationInstanceNumber; "PathToSAPControl" = $PathToSAPControl }
            
            $resource = Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceName $VMName
            #New-AzTag -ResourceId $resource.id -Tag $tags
            Update-AzTag -ResourceId $resource.id -Tag $tags -Operation Merge

            # Create Credetnials in Azure Automation Secure Area
            $User = $SAPSID.Trim().ToLower() + "adm"
            Write-WithTime "Creating credentials in Azure automation account secure area for user '$User' ...."
            New-AzSAPsidadmUserAutomationCredential -AutomationAccountResourceGroupName $AutomationAccountResourceGroupName -AutomationAccountName $AutomationAccountName -SAPSID $SAPSID -SAPsidadmUserPassword $SAPsidadmUserPassword                                  
        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function New-AzSAPSystemSAPJavaApplicationServerInstanceWindowsTags {
    <#
            .SYNOPSIS
            Set Tags on Standalone JavaApplication Server instance on Windows.
                     
            .DESCRIPTION
            Set Tags on Standalone Java Application Server instance on Windows.
                     
            .PARAMETER ResourceGroupName
            Resource Group Name of the VM.
     
            .PARAMETER VMName
            VM name.
 
            .PARAMETER SAPSID
            SAP system SID.
                 
            .PARAMETER SAPApplicationInstanceNumber
            SAP 'J' Java Application Server Instance Number.
 
            .EXAMPLE
           # Set Tags on Standalone HANA belonging to an SAP system
           $ResourceGroupName = "gor-eastus2"
           $VMName = "ts2-di0"
 
           New-AzSAPSystemSAPJavaApplicationServerInstanceWindowsTags -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID "TS1" -SAPApplicationInstanceNumber 1 -PathToSAPControl "S:\usr\sap\AB1\J01\exe\sapcontrol.exe" -AutomationAccountResourceGroupName "rg-autom-account" -AutomationAccountName "sap-automat-acc" -SAPsidadmUserPassword "MyPass789j$&"
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,
        
        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 2)]
        [string] $SAPApplicationInstanceNumber,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $PathToSAPControl,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$AutomationAccountResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $AutomationAccountName,        
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $SAPsidadmUserPassword
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
                                                         
            $SAPApplicationInstanceType = "SAP_J"            

            # Create VM Tags
            Write-WithTime "Creating '$SAPApplicationInstanceType' Tags on VM '$VMName' in resource group '$ResourceGroupName' ...."
            $tags = @{"SAPSystemSID" = $SAPSID; "SAPApplicationInstanceType" = $SAPApplicationInstanceType ; "SAPApplicationInstanceNumber" = $SAPApplicationInstanceNumber; "PathToSAPControl" = $PathToSAPControl }
            
            $resource = Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceName $VMName
            #New-AzTag -ResourceId $resource.id -Tag $tags
            Update-AzTag -ResourceId $resource.id -Tag $tags -Operation Merge

            # Create Credetnials in Azure Automation Secure Area
            $User = $SAPSID.Trim().ToLower() + "adm"
            Write-WithTime "Creating credentials in Azure automation account secure area for user '$User' ...."
            New-AzSAPsidadmUserAutomationCredential -AutomationAccountResourceGroupName $AutomationAccountResourceGroupName -AutomationAccountName $AutomationAccountName -SAPSID $SAPSID -SAPsidadmUserPassword $SAPsidadmUserPassword                                  
        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function New-AzSAPStandaloneSQLServerTags {
    <#
            .SYNOPSIS
            Set Tags on Standalone SAP SQL Server.
                     
            .DESCRIPTION
            Set Tags on Standalone SAP SQL Server in distributed SAP installation.
                     
            .PARAMETER ResourceGroupName
            Resource Group Name of the VM.
     
            .PARAMETER VMName
            VM name.
 
            .PARAMETER SAPSID,
            SAPSID.
 
            .PARAMETER DBInstanceName
            SQL Server DB Instance Name. Empty string is deafult SQL Server instance.
                 
            .EXAMPLE
           # Set Tags on Standalone HANA belonging to an SAP system
           $ResourceGroupName = "gor-linux-eastus2"
           $VMName = "ts2-db"
 
           New-AzSAPStandaloneSQLServerTags -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID "TS1" -DBInstanceName $DBInstanceName
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,
            
        [Parameter(Mandatory=$True, HelpMessage="SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3,3)]
        [string] $SAPSID,

        [Parameter(Mandatory=$false, HelpMessage="SQL Server DB Instance Name. Empty string is deafult SQL instance name.")] 
        [string] $DBInstanceName = ""
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                                                                               
            $SAPDBMSType = "SQLServer"
            
            $tags = @{"SAPSystemSID" = $SAPSID; "DBInstanceName" = $DBInstanceName; "SAPDBMSType" = $SAPDBMSType; }
            
            $resource = Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceName $VMName
            #New-AzTag -ResourceId $resource.id -Tag $tags
            Update-AzTag -ResourceId $resource.id -Tag $tags -Operation Merge
        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function New-AzSAPCentralSystemSQLServerTags {
    <#
            .SYNOPSIS
            Set Tags on Standalone SAP 'ASCS' instance on Windows.
                     
            .DESCRIPTION
            Set Tags on Standalone SAP 'ASCS' instance on Windows.
                     
            .PARAMETER ResourceGroupName
            Resource Group Name of the VM.
     
            .PARAMETER VMName
            VM name.
 
            .PARAMETER SAPSID
            SAP system SID.
                 
            .PARAMETER SAPApplicationInstanceNumber
            SAP ASCS Instance Number.
 
            .PARAMETER DBInstanceName
            SQL Server DB Instance Name. Empty string is deafult SQL Server instance.
 
            .EXAMPLE
           # Set Tags on Standalone HANA belonging to an SAP system
           $ResourceGroupName = "gor-linux-eastus2"
           $VMName = "ts2-db"
 
           New-AzSAPCentralSystemSQLServerTags -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID "TS1" -SAPApplicationInstanceNumber 1 -DBInstanceName TS1
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,
        
        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 2)]   
        [string] $SAPApplicationInstanceNumber,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $PathToSAPControl,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$AutomationAccountResourceGroupName,

        [Parameter(Mandatory=$false, HelpMessage="SQL Server DB Instance Name. Empty string is deafult SQL instance name.")] 
        [string] $DBInstanceName = "",
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $AutomationAccountName,        
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $SAPsidadmUserPassword
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
                                                         
            $SAPApplicationInstanceType = "SAP_ASCS"      
            $SAPDBMSType = "SQLServer"                     

            # Create VM Tags
            Write-WithTime "Creating '$SAPApplicationInstanceType' Tags on VM '$VMName' in resource group '$ResourceGroupName' ...."
            $tags = @{"SAPSystemSID" = $SAPSID; "SAPApplicationInstanceType" = $SAPApplicationInstanceType ; "SAPApplicationInstanceNumber" = $SAPApplicationInstanceNumber; "PathToSAPControl" = $PathToSAPControl ; "DBInstanceName" = $DBInstanceName; "SAPDBMSType" = $SAPDBMSType; }
            
            $resource = Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceName $VMName

            #New-AzTag -ResourceId $resource.id -Tag $tags
            Update-AzTag -ResourceId $resource.id -Tag $tags -Operation Merge

            # Create Credetnials in Azure Automation Secure Area
            $User = $SAPSID.Trim().ToLower() + "adm"
            Write-WithTime "Creating credentials in Azure automation account secure area for user '$User' ...."
            New-AzSAPsidadmUserAutomationCredential -AutomationAccountResourceGroupName $AutomationAccountResourceGroupName -AutomationAccountName $AutomationAccountName -SAPSID $SAPSID -SAPsidadmUserPassword $SAPsidadmUserPassword                                  
        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    


function Get-AzAutomationSAPPSCredential {
    <#
            .SYNOPSIS
            Get Azure Automation Account credential user name and password.
                     
            .DESCRIPTION
            Get Azure Automation Account credential user name and password.
                     
            .PARAMETER CredentialName
            Credential Name.
     
            .EXAMPLE
           Get-AzAutomationSAPPSCredential -CredentialName "pr1adm"
        #>

    [CmdletBinding()]
    param(                        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $CredentialName
      
    )
                
    BEGIN {}
                    
    PROCESS {
        try {                               
                                                         
            $myCredential = Get-AutomationPSCredential -Name $CredentialName
            $userName = $myCredential.UserName
            $securePassword = $myCredential.Password
            $password = $myCredential.GetNetworkCredential().Password

            write-output "user name: $userName"
            write-output "password : $password"

            $obj = New-Object -TypeName psobject
            

            $obj | add-member  -NotePropertyName "UserName" -NotePropertyValue $userName 
            $obj | add-member  -NotePropertyName "Password" -NotePropertyValue $password
            
            Write-Output $obj
        }
        catch {
            Write-Error  $_.Exception.Message
        }
                
    }
                
    END {}
}    

function Stop-AzSAPApplicationServerLinux {
    <#
        .SYNOPSIS
        Stop SAP Application server on Linux.
                     
        .DESCRIPTION
        Stop SAP Application server on Linux.
                     
        .PARAMETER ResourceGroupName
        Resource Group Name of the SAP instance VM.
 
        .PARAMETER VMName
        VM name where SAP instance is installed.
 
        .PARAMETER SAPInstanceNumber
        SAP Instance Number to Connect
 
        .PARAMETER SAPSID
        SAP SID
 
        .PARAMETER SoftShutdownTimeInSeconds
        Soft shutdown time for SAP system to stop.
 
        .EXAMPLE
        Stop-AzSAPApplicationServerLinux -ResourceGroupName "myRG" -VMName SAPApServerVM -SAPInstanceNumber 0 -SAPSID "TS2" -SoftShutdownTimeInSeconds 180
    #>


    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,        

        [Parameter(Mandatory = $True)]
        [ValidateRange(0, 99)]
        [ValidateLength(1, 2)]
        [string] $SAPInstanceNumber,

        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,

        [Parameter(Mandatory = $False)] 
        [int] $SoftShutdownTimeInSeconds = "300",

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False,

        [switch] $PrintOutputWithWriteHost
    )

    BEGIN {}
    
    PROCESS {
        try {   
            $SAPSidUser = $SAPSID.ToLower() + "adm"            

            # Stop SAP ABAP Application Server

            if ($PrintOutputWithWriteHost) {                
                Write-WithTime_Using_WriteHost "Stopping SAP SID '$SAPSID' application server with instance number '$SAPInstanceNumber' on VM '$VMName' , with application time out $SoftShutdownTimeInSeconds seconds ..."
            }else {                
                Write-WithTime "Stopping SAP SID '$SAPSID' application server with instance number '$SAPInstanceNumber' on VM '$VMName' , with application time out $SoftShutdownTimeInSeconds seconds ..."    
            }
            
            $Command = "su --login $SAPSidUser -c 'sapcontrol -nr $SAPInstanceNumber -function Stop $SoftShutdownTimeInSeconds $SoftShutdownTimeInSeconds'"
            
            if ($PrintExecutionCommand -eq $True) {
                if ($PrintOutputWithWriteHost) {                   
                    Write-WithTime_Using_WriteHost "Executing command '$Command' "
                }else {                    
                    Write-WithTime "Executing command '$Command' "
                }                
            }

            $Command | Out-File "command.txt"

            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunShellScript  -ScriptPath command.txt

            $ret.Value[0].Message

            [int] $SleepTime = $SoftShutdownTimeInSeconds + 60

            if ($PrintOutputWithWriteHost) {                
                Write-WithTime_Using_WriteHost "Waiting $SoftShutdownTimeInSeconds seconds for SAP application server with SAP SID '$SAPSID' and instance number '$SAPInstanceNumber' to stop ..."
            }else {                
                Write-WithTime "Waiting $SoftShutdownTimeInSeconds seconds for SAP application server with SAP SID '$SAPSID' and instance number '$SAPInstanceNumber' to stop ..."
            }
            
            Start-Sleep $SleepTime

            if ($PrintOutputWithWriteHost) {                
                Write-WithTime_Using_WriteHost "SAP Application server with SAP SID '$SAPSID' and instance number '$SAPInstanceNumber' on VM '$VMName' and Azure resource group '$ResourceGroupName' stopped."
            }else {                
                Write-WithTime "SAP Application server with SAP SID '$SAPSID' and instance number '$SAPInstanceNumber' on VM '$VMName' and Azure resource group '$ResourceGroupName' stopped."
            }
            
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}

function Stop-AzSAPApplicationServerWindows {
    <#
        .SYNOPSIS
        Stop SAP Application server on Linux.
                     
        .DESCRIPTION
        Stop SAP Application server on Linux.
                     
        .PARAMETER ResourceGroupName
        Resource Group Name of the SAP instance VM.
 
        .PARAMETER VMName
        VM name where SAP instance is installed.
 
        .PARAMETER SAPInstanceNumber
        SAP Instance Number to Connect
 
        .PARAMETER SAPSID
        SAP SID
 
        .PARAMETER SoftShutdownTimeInSeconds
        Soft shutdown time for SAP system to stop.
 
        .EXAMPLE
        Stop-AzSAPApplicationServerLinux -ResourceGroupName "myRG" -VMName SAPApServerVM -SAPSID "TS2" -SAPInstanceNumber 0 -PathToSAPControl "C:\usr\sap\PR2\D00\exe\sapcontrol.exe" -SAPSidPwd "Mypwd36" -SoftShutdownTimeInSeconds 180
    #>


    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,        

        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,

        [Parameter(Mandatory = $True)]
        [ValidateLength(1, 2)] 
        [string] $SAPInstanceNumber,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $SAPSidPwd,    

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $PathToSAPControl,

        [Parameter(Mandatory = $False)] 
        [int] $SoftShutdownTimeInSeconds = "300",

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False,

        [switch] $PrintOutputWithWriteHost
    )

    BEGIN {}
    
    PROCESS {
        try {   
            $SAPSidUser = $SAPSID.ToLower() + "adm"            

            # Stop SAP ABAP Application Server

            if ($PrintOutputWithWriteHost) {                
                Write-WithTime_Using_WriteHost "Stopping SAP SID '$SAPSID' application server with instance number '$SAPInstanceNumber' on VM '$VMName' , with application time out $SoftShutdownTimeInSeconds seconds ..."
            }else {                
                Write-WithTime "Stopping SAP SID '$SAPSID' application server with instance number '$SAPInstanceNumber' on VM '$VMName' , with application time out $SoftShutdownTimeInSeconds seconds ..."
            }            
            
            $Command       = "$PathToSAPControl -nr $SAPInstanceNumber -user $SAPSidUser '$SAPSidPwd' -function Stop $SoftShutdownTimeInSeconds $SoftShutdownTimeInSeconds"
            $CommandToPrint = "$PathToSAPControl -nr $SAPInstanceNumber -user $SAPSidUser '***pwd****' -function Stop $SoftShutdownTimeInSeconds $SoftShutdownTimeInSeconds"

            if ($PrintExecutionCommand -eq $True) {
                if ($PrintOutputWithWriteHost) {                
                    Write-WithTime_Using_WriteHost "Executing command '$CommandToPrint' "
                }else {                
                    Write-WithTime "Executing command '$CommandToPrint' "
                }                
            }

            $Command | Out-File "command.txt"

            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunPowerShellScript -ScriptPath command.txt

            $ret.Value[0].Message

            [int] $SleepTime = $SoftShutdownTimeInSeconds + 60

            if ($PrintOutputWithWriteHost) {                
                Write-WithTime_Using_WriteHost "Waiting $SoftShutdownTimeInSeconds seconds for SAP application server with SAP SID '$SAPSID' and instance number '$SAPInstanceNumber' to stop ..."
            }else {                
                Write-WithTime "Waiting $SoftShutdownTimeInSeconds seconds for SAP application server with SAP SID '$SAPSID' and instance number '$SAPInstanceNumber' to stop ..."
            }
            
            Start-Sleep $SleepTime

            if ($PrintOutputWithWriteHost) {                
                Write-WithTime_Using_WriteHost "SAP Application server with SAP SID '$SAPSID' and instance number '$SAPInstanceNumber' on VM '$VMName' and Azure resource group '$ResourceGroupName' stopped."
            }else {                
                Write-WithTime "SAP Application server with SAP SID '$SAPSID' and instance number '$SAPInstanceNumber' on VM '$VMName' and Azure resource group '$ResourceGroupName' stopped."
            }            
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}


function Start-AzSAPApplicationServerLinux {
    <#
        .SYNOPSIS
        Start SAP Application server on Linux.
                     
        .DESCRIPTION
        Start SAP Application server on Linux.
                     
        .PARAMETER ResourceGroupName
        Resource Group Name of the SAP instance VM.
 
        .PARAMETER VMName
        VM name where SAP instance is installed.
 
        .PARAMETER SAPInstanceNumber
        SAP Instance Number to Connect
 
        .PARAMETER SAPSID
        SAP SID
 
        .PARAMETER WaitTime
        WaitTime for SAP application server to start.
 
        .EXAMPLE
        Start-AzSAPApplicationServerLinux -ResourceGroupName "myRG" -VMName SAPApServerVM -SAPInstanceNumber 0 -SAPSID "TS2" -WaitTime 180
    #>


    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True)]
        [ValidateLength(1, 2)]        
        [string] $SAPInstanceNumber,

        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,

        [Parameter(Mandatory = $False)] 
        [int] $WaitTime = "300",

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False
    )

    BEGIN {}
    
    PROCESS {
        try {   
            $SAPSidUser = $SAPSID.ToLower() + "adm"            

            # Stop SAP ABAP Application Server
            Write-WithTime "Starting SAP SID '$SAPSID' application server with instance number '$SAPInstanceNumber' on VM '$VMName' , with wait time $WaitTime seconds ..."

            $Command = "su --login $SAPSidUser -c 'sapcontrol -nr $SAPInstanceNumber -function Start'"            
            
            if ($PrintExecutionCommand -eq $True) {
                Write-WithTime "Executing command '$Command' "
            }

            $Command | Out-File "command.txt"

            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunShellScript  -ScriptPath command.txt

            $ret.Value[0].Message            
            
            Write-WithTime "Waiting $WaitTime seconds for SAP application server with SAP SID '$SAPSID' and instance number '$SAPInstanceNumber' to start ..."

            Start-Sleep $WaitTime

            Write-WithTime "SAP Application server with SAP SID '$SAPSID' and instance number '$SAPInstanceNumber' on VM '$VMName' and Azure resource group '$ResourceGroupName' started."
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}


function Start-AzSAPApplicationServerWindows {
   <#
        .SYNOPSIS
        Start SAP Application server on Windows.
                     
        .DESCRIPTION
        Start SAP Application server on Windows.
                     
        .PARAMETER ResourceGroupName
        Resource Group Name of the SAP instance VM.
 
        .PARAMETER VMName
        VM name where SAP instance is installed.
         
        .PARAMETER SAPSID
        SAP SID
 
        .PARAMETER SAPInstanceNumber
        SAP Instance Number to Connect
 
        .PARAMETER SAPSidPwd
        SAP <sid>adm user password
 
        .PARAMETER PathToSAPControl
        Full path to SAP Control executable.
 
        .PARAMETER SoftShutdownTimeInSeconds
        Soft shutdown time for SAP system to stop.
 
        .EXAMPLE
        Start-AzSAPApplicationServerWindows -ResourceGroupName "myRG" -VMName SAPApServerVM -SAPSID "TS2" -SAPInstanceNumber 0 -PathToSAPControl "C:\usr\sap\PR2\D00\exe\sapcontrol.exe" -SAPSidPwd "Mypwd36" -WaitTime 180
    #>


    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,

        [Parameter(Mandatory = $True)]
        [ValidateLength(1, 2)] 
        [string] $SAPInstanceNumber,        

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $SAPSidPwd,    

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $PathToSAPControl,

        [Parameter(Mandatory = $False)] 
        [int] $WaitTime = "300",

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False
    ) 

    BEGIN {}
    
    PROCESS {
        try {   
            $SAPSidUser = $SAPSID.ToLower() + "adm"            

            # Start SAP ABAP Application Server
            Write-WithTime "Starting SAP SID '$SAPSID' application server with instance number '$SAPInstanceNumber' on VM '$VMName' , with wait time $WaitTime seconds ..."
            
            $Command        = "$PathToSAPControl -nr $SAPInstanceNumber -user $SAPSidUser '$SAPSidPwd' -function Start"
            $CommandToPrint = "$PathToSAPControl -nr $SAPInstanceNumber -user $SAPSidUser '***pwd***' -function Start"
            
            if ($PrintExecutionCommand -eq $True) {
                Write-WithTime "Executing command '$CommandToPrint' "
            }

            $Command | Out-File "command.txt"

            $ret = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName  -CommandId RunPowerShellScript -ScriptPath command.txt

            $ret.Value[0].Message            
            
            Write-WithTime "Waiting $WaitTime seconds for SAP application server with SAP SID '$SAPSID' and instance number '$SAPInstanceNumber' to start ..."

            Start-Sleep $WaitTime

            Write-WithTime "SAP Application server with SAP SID '$SAPSID' and instance number '$SAPInstanceNumber' on VM '$VMName' and Azure resource group '$ResourceGroupName' started."
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}

function Start-AzSAPApplicationServer {
    <#
    .SYNOPSIS
    Start SAP application server running on VM.
     
    .DESCRIPTION
    Start SAP application server running on VM.
     
    .PARAMETER ResourceGroupName
    Azure Resource Group Name
 
    .PARAMETER ResourceGroupName
    Azure VM Name
 
    .PARAMETER WaitTime
    Number of seconds to wait for SAP system to start.
 
    .PARAMETER PrintExecutionCommand
    If set to $True it will pring the run command.
 
    .EXAMPLE
    Start-AzSAPApplicationServer -ResourceGroupName "AzResourceGroup" -VMName "VMname" -WaitTime 60
 #>


    [CmdletBinding()]
    param(       
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,
        
        [Parameter(Mandatory = $False)] 
        [int] $WaitTime = "300",

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False        
    )

    BEGIN {}
    
    PROCESS {
        try {   
            # get SAP server datza from VM Tags
            $SAPApplicationServerData = Get-AzSAPApplicationInstanceData -ResourceGroupName $ResourceGroupName -VMName $VMName  

            #Write-Host $SAPApplicationServerData

            if ($SAPApplicationServerData.OSType -eq "Linux") {                  
                Start-AzSAPApplicationServerLinux  -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPInstanceNumber $SAPApplicationServerData.SAPApplicationInstanceNumber -SAPSID $SAPApplicationServerData.SAPSID -WaitTime $WaitTime -PrintExecutionCommand $PrintExecutionCommand                            
            }
            elseif ($SAPAPPLicationServerData.OSType -eq "Windows") {                
                Start-AzSAPApplicationServerWindows  -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID $SAPApplicationServerData.SAPSID -SAPInstanceNumber $SAPApplicationServerData.SAPApplicationInstanceNumber  -PathToSAPControl $SAPApplicationServerData.PathToSAPControl -SAPSidPwd  $SAPApplicationServerData.SAPSIDPassword -WaitTime $WaitTime -PrintExecutionCommand $PrintExecutionCommand
            }           
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}

function Stop-AzSAPApplicationServer {
    <#
    .SYNOPSIS
    Start SAP application server running on VM.
     
    .DESCRIPTION
    Start SAP application server running on VM.
     
    .PARAMETER ResourceGroupName
    Azure Resource Group Name
 
    .PARAMETER ResourceGroupName
    Azure VM Name
 
    .PARAMETER SoftShutdownTimeInSeconds
    Soft shutdown time for SAP system to stop.
 
    .PARAMETER PrintExecutionCommand
    If set to $True it will pring the run command.
 
    .EXAMPLE
    Start-AzSAPApplicationServer -ResourceGroupName "AzResourceGroup" -VMName "VMname" -WaitTime 60
 #>


    [CmdletBinding()]
    param(       
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $ResourceGroupName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()] 
        [string] $VMName,
        
        [Parameter(Mandatory = $False)] 
        [int] $SoftShutdownTimeInSeconds = "300",

        [Parameter(Mandatory = $False)] 
        [bool] $PrintExecutionCommand = $False        
    )

    BEGIN {}
    
    PROCESS {
        try {   
            # get SAP server data from VM Tags
            $SAPApplicationServerData = Get-AzSAPApplicationInstanceData -ResourceGroupName $ResourceGroupName -VMName $VMName  

            #Write-Host $SAPApplicationServerData

            if ($SAPApplicationServerData.OSType -eq "Linux") {                  
                Stop-AzSAPApplicationServerLinux  -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPInstanceNumber $SAPApplicationServerData.SAPApplicationInstanceNumber -SAPSID $SAPApplicationServerData.SAPSID -SoftShutdownTimeInSeconds $SoftShutdownTimeInSeconds -PrintExecutionCommand $PrintExecutionCommand                            
            }
            elseif ($SAPAPPLicationServerData.OSType -eq "Windows") {                
                Stop-AzSAPApplicationServerWindows  -ResourceGroupName $ResourceGroupName -VMName $VMName -SAPSID $SAPApplicationServerData.SAPSID -SAPInstanceNumber $SAPApplicationServerData.SAPApplicationInstanceNumber  -PathToSAPControl $SAPApplicationServerData.PathToSAPControl -SAPSidPwd  $SAPApplicationServerData.SAPSIDPassword -SoftShutdownTimeInSeconds $SoftShutdownTimeInSeconds -PrintExecutionCommand $PrintExecutionCommand
            }           
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}


function Confirm-AzResoureceGroupExist {
   <#
    .SYNOPSIS
    Check if Azure resource Group exists.
     
    .DESCRIPTION
    Check if Azure resource Group exists.
     
    .PARAMETER ResourceGroupName
    Azure Resource Group Name
 
    .EXAMPLE
    Confirm-AzResoureceGroupExist -ResourceGroupName "AzResourceGroupName"
 #>


    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $ResourceGroupName                      
        
    )

    BEGIN {}
    
    PROCESS {
        try {               
            $RG = Get-AzResourceGroup -Name $ResourceGroupName -ErrorVariable -notPresent  -ErrorAction SilentlyContinue

            if ($RG -eq $null) {                
                Write-Error "Azure resource group '$ResourceGroupName' do not exists. Check your input parameter 'RESOURCEGROUPNAME'."   
                exit             
            }
            else {
                Write-WithTime "Azure resource group '$ResourceGroupName' exist."
            }
        }
        catch {
           
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}

function Confirm-AzVMExist {
<#
    .SYNOPSIS
    Check if Azure VM exists.
     
    .DESCRIPTION
    Check if Azure VM exists.
     
    .PARAMETER ResourceGroupName
    Azure Resource Group Name
 
    .PARAMETER VMName
    Azure VM Name
 
    .EXAMPLE
    Confirm-AzVMExist -ResourceGroupName "AzResourceGroupName" -VMName "MyVMName"
 #>

    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName                      
        
    )

    BEGIN {}
    
    PROCESS {
        try {               
            $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName  -ErrorVariable -notPresent -ErrorAction SilentlyContinue

            if ($VM -eq $null) {                
                Write-Error "Azure virtual machine '$VMName' in Azure resource group '$ResourceGroupName' do not exists. Check your VM name and resource group name input parameter."   
                exit             
            }
            else {
                Write-WithTime_Using_WriteHost "Azure VM '$VMName' in Azure resource group '$ResourceGroupName' exist."
            }
        }
        catch {           
            Write-Error  $_.Exception.Message
        }

    }

    END {}
}

function Get-AzSAPApplicationInstanceData {
    <#
    .SYNOPSIS
    Get SAP Application Instance Data from tags from one VM.
     
    .DESCRIPTION
    Get SAP Application Instance Data from tags from one VM.
      
    .PARAMETER ResourceGroupName
    Resource Group Name of the VM.
     
    .PARAMETER VMName
    VM name.
         
    .EXAMPLE
    # Collect SAP VM instances with the same Tag
    $SAPAPPLicationServerData = Get-AzSAPApplicationInstanceData -ResourceGroupName "AzResourceGroup" -VMName "SAPApplicationServerVMName"
 #>


    [CmdletBinding()]
    param(        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
           
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName
    )

    BEGIN {}
    
    PROCESS {
        try {   
                                  
            $SAPSID = Get-AzVMTagValue -ResourceGroupName $ResourceGroupName -VMName $VMName  -KeyName "SAPSystemSID"  
            if ($SAPSID -eq $null) {
                Throw "Tag 'SAPSystemSID' on VM '$VMName' in Azure resource group $ResourceGroupName not found."
            }            
            #Write-Host "SAPSID = $SAPSID"

            $SAPApplicationInstanceNumber = Get-AzVMTagValue -ResourceGroupName $ResourceGroupName -VMName $VMName  -KeyName "SAPApplicationInstanceNumber"  
            if ($SAPApplicationInstanceNumber -eq $null) {
                Throw "Tag 'SAPApplicationInstanceNumber' on VM '$VMName' in Azure resource group $ResourceGroupName not found."

            }
            #Write-Host "SAPApplicationInstanceNumber = $SAPApplicationInstanceNumber"

            $SAPApplicationInstanceType = Get-AzVMTagValue -ResourceGroupName $ResourceGroupName -VMName $VMName  -KeyName "SAPApplicationInstanceType"  
            if ($SAPApplicationInstanceType -eq $null) {
                Throw "Tag 'SAPApplicationInstanceType' on VM '$VMName' in Azure resource group $ResourceGroupName not found."
            }            
            #Write-Host "SAPApplicationInstanceType = $SAPApplicationInstanceType"

            If (-Not (Test-SAPApplicationInstanceIsApplicationServer $SAPApplicationInstanceType)) {
                Throw "SAP Instance type '$SAPApplicationInstanceType' is not an SAP application server."
            }

            $OSType = Get-AzVMOSType -VMName $VMName -ResourceGroupName $ResourceGroupName

            if ($OSType -eq "Windows") {                                
                $SIDADMUser = $SAPSID.Trim().ToLower() + "adm"
                $SAPSIDCredentials = Get-AzAutomationSAPPSCredential -CredentialName  $SIDADMUser  
                $SAPSIDPassword = $SAPSIDCredentials.Password
                $PathToSAPControl = Get-AzVMTagValue -ResourceGroupName $ResourceGroupName  -VMName $VMName  -KeyName "PathToSAPControl"  
            }

            $obj = New-Object -TypeName psobject

            $obj | add-member  -NotePropertyName "SAPSID"                       -NotePropertyValue $SAPSID  
            $obj | add-member  -NotePropertyName "VMName"                       -NotePropertyValue $VMName  
            $obj | add-member  -NotePropertyName "ResourceGroupName"            -NotePropertyValue $ResourceGroupName  
            $obj | add-member  -NotePropertyName "SAPApplicationInstanceNumber" -NotePropertyValue $SAPApplicationInstanceNumber
            $obj | add-member  -NotePropertyName "SAPInstanceType"              -NotePropertyValue $SAPApplicationInstanceType
            $obj | add-member  -NotePropertyName "OSType"                       -NotePropertyValue $OSType 

            if ($OSType -eq "Windows") {
                $obj | add-member  -NotePropertyName "SAPSIDPassword"           -NotePropertyValue $SAPSIDPassword
                $obj | add-member  -NotePropertyName "PathToSAPControl"         -NotePropertyValue $PathToSAPControl               
            }

            # Return formated object
            Write-Output $obj            
                                
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}
function Test-SAPApplicationInstanceIsApplicationServer {
    <#
    .SYNOPSIS
    If SAP Application Instance is application server['SAP_D','SAP_DVEBMGS','SAP_J'] , retruns $True. Otherwise return $False.
     
    .DESCRIPTION
   If SAP Application Instance is application server['SAP_D','SAP_DVEBMGS','SAP_J'] , retruns $True. Otherwise return $False.
     
    .PARAMETER SAPApplicationInstanceType
    SAP ApplicationInstance Type ['SAP_D','SAP_DVEBMGS','SAP_J']
     
    .EXAMPLE
   Test-SAPApplicationInstanceIsApplicationServer "SAP_D"
     
 #>


    [CmdletBinding()]
    param(        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$SAPApplicationInstanceType
                
    )

    BEGIN {}
    
    PROCESS {
        try {   
                      
            switch ($SAPApplicationInstanceType) {
                "SAP_D" { return $True }
                "SAP_DVEBMGS" { return $True }
                "SAP_J" { return $True }
                Default { return $False }
            }
            
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }

    END {}
}

function Stop-AzVMAndPrintStatus {
    <#
    .SYNOPSIS
    Stop Azure VM and printa status.
     
    .DESCRIPTION
    Stop Azure VM and printa status.
     
    .PARAMETER ResourceGroupName
    VM Azure Resource Group Name.
     
    .PARAMETER VMName
    VM Name.
     
    .EXAMPLE
    Stop-AzVMAndPrintStatus -ResourceGroupName "PR1-RG" -VMName "PR1-DB"
 #>


    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName

        
    )

    BEGIN {}
    
    PROCESS {
        try {   
            
            Write-WithTime "Stopping VM '$VMName' in Azure Resource Group '$ResourceGroupName' ..."
            Stop-AzVM  -ResourceGroupName $ResourceGroupName -Name $VMName -WarningAction "SilentlyContinue" -Force

            $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName -Status
            $VMStatus = $VM.Statuses[1].DisplayStatus
            Write-WithTime "Virtual Machine '$VMName' status: $VMStatus" 
        }
        catch {
            Write-Error  $_.Exception.Message
        }

    }
    END {}
}

function Start-AzVMAndPrintStatus {
    <#
    .SYNOPSIS
    Start Azure VM and printa status.
     
    .DESCRIPTION
    Start Azure VM and printa status.
     
    .PARAMETER ResourceGroupName
    VM Azure Resource Group Name.
     
    .PARAMETER VMName
    VM Name.
     
    .PARAMETER SleepTimeAfterVMStart
    Wait time in seconds after VM is started.
     
    .EXAMPLE
    # Start VM and wait for 60 seconds [default]
    Start-AzVMAndPrintStatus -ResourceGroupName "PR1-RG" -VMName "PR1-DB"
 
    .EXAMPLE
    # Start VM and do not wait
    Start-AzVMAndPrintStatus -ResourceGroupName "PR1-RG" -VMName "PR1-DB" -SleepTimeAfterVMStart 0
 #>


    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
              
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMName,

        [Parameter(Mandatory = $false)]             
        [int] $SleepTimeAfterVMStart = 60

        
    )

    BEGIN {}
    
    PROCESS {
        try {   
             # Start VM
             Write-WithTime "Starting VM '$VMName' in Azure Resource Group '$ResourceGroupName' ..."
             Start-AzVM  -ResourceGroupName $ResourceGroupName -Name $VMName -WarningAction "SilentlyContinue"

             $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName -Status
             $VMStatus = $VM.Statuses[1].DisplayStatus
             #Write-Host ""
             Write-WithTime "Virtual Machine '$VMName' status: $VMStatus"

            # Wait for $SleepTimeAfterVMStart seconds after VM is started
            Start-Sleep $SleepTimeAfterVMStart
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    }
    END {}
}

# https://docs.microsoft.com/en-us/azure/load-balancer/quickstart-create-standard-load-balancer-powershell
# https://docs.microsoft.com/en-us/azure/load-balancer/upgrade-basicinternal-standard


Function Move-AzVMToAvailabilitySetAndOrProximityPlacementGroup {
<#
.SYNOPSIS
    Moves a VM into an:
 
    - Availability Set
    - Proximity Placement Group
    - Availability Set and Proximity Placement Group
     
 
.DESCRIPTION
    The script deletes the VM and recreates it preserving networking and storage configuration.
 
    There is no need to reinstall the operating system.
     
    IMPORTANT: The script does not preserve VM extensions.
                Also, the script does not spport VMs with public IP addresses.
                Zonal VMs are not supported, because you cannot combine Availability Set and Zone.
                VM, Availabity Set and VM must be members of the same Azure resource group.
                Proximity Placement Group can be member of some other resource group.
         
    IMPORTANT: SAP context
 
    You can use the script to:
 
    - Move SAP Application Servers to new Availability Set
 
    - Move SAP Application Servers to new Availability Set and Proximity Placement Group:
 
        - It can be used in Azure Zones context, where you move SAP Application Server to Zone.
            One group of SAP Application Servers are indirectly part of Zone1 (via AvSet1 and PPGZone1), and other part of SAP Application Servers are indirectly part of Zone2 (via AvSet2 and PPGZone2).
            First ancor VM (this is DBMS VM) is alreday deplyed in a Zone and same Proximity Placement Group
 
        - It can be used to move an SAP Application Server from current AvSet1 and PPGZone1 to AvSet2 and PPGZone2, e.g. indirectly from Zone1 to Zone2.
            First ancor VM (this is DBMS VM) is alreday deplyed in a Zone2 and same Proximity Placement Group 2 (PPGZone2).
 
        - It can be used in non-Zonal context, where group of SAP Application Servers are part of new Av Set and Proximity Placement Group, together with the SAP ASCS and DB VM that are part of one SAP SID.
 
    - Group all VMs to Proximity Placement Group
 
.PARAMETER VMResourceGroupName
Resource Group Name of the VM, and Availability Set.
     
.PARAMETER VirtualMachineName
Virtual Machine Name.
 
.PARAMETER AvailabilitySetName
Availability Set Name.
 
.PARAMETER PPGResourceGroupName
Resource Group Name of the Proximity Placemen tGroup
 
.PARAMETER ProximityPlacementGroupName
Proximity PlacementGroup Name
 
.PARAMETER DoNotCopyTags
Switch paramater. If specified, VM tags will NOT be copied.
 
.PARAMETER NewVMSize
If NewVMSize is specified , VM will be set to a new VM size. Otherwise, original VM size will be used.
 
.PARAMETER KeepSameDiskNames
Keep the original disk names. If not specified, generate new disk names.
 
.PARAMETER Force
Forces the command to run without asking for user confirmation.
     
.EXAMPLE
# THis is example that can be used for moving (indirectly via PPG) SAP Application Servers to a desired Azure zone
# Move VM 'VM1' to Azure Availability Set and Proximity Placement GroupName (PPG)
# Proximity Placement Group must alreday exist
# Availability Set must exist and be associated to Proximity Placement Group
# VM tags will not be copied : swicth parameter -DoNotCopyTags is set
 
# If Av Set doesn't exist, you can create it like this:
$Location = "eastus"
$AzureAvailabilitySetName = "TargetAvSetZone1"
$ResourceGroupName = "gor-Zone-Migration"
$ProximityPlacementGroupName = "PPGZone1"
$PPGResourceGroupName "MyPPGResourceGroupName"
$PlatformFaultDomainCount = 3
$PlatformUpdateDomainCount = 2
 
$PPG = Get-AzProximityPlacementGroup -ResourceGroupName $PPGResourceGroupName -Name $ProximityPlacementGroupName
New-AzAvailabilitySet -Location $Location -Name $AzureAvailabilitySetName -ResourceGroupName $ResourceGroupName -PlatformFaultDomainCount $PlatformFaultDomainCount -PlatformUpdateDomainCount $PlatformUpdateDomainCount -ProximityPlacementGroupId $PPG.Id -Sku Aligned
 
# Move VM
Move-AzVMToAvailabilitySetAndOrProximityPlacementGroup -VMResourceGroupName "gor-Zone-Migration" -VirtualMachineName "VM1" -AvailabilitySetName "TargetAvSetZone1" -ProximityPlacementGroupName "PPGZone1" -PPGResourceGroupName "MyPPGResourceGroupName" -DoNotCopyTags
 
 
.EXAMPLE
# Move VM to Proximity Placement Group
# Proximity Placement Group must alreday exist
# VM will be set to NEW VM size, e.g. not original VM size , because 'NewVMSize' is specified
Move-AzVMToAvailabilitySetAndOrProximityPlacementGroup -ResourceGroupName "gor-Zone-Migration" -VirtualMachineName "VM1" -ProximityPlacementGroupName "PPGZone1" -NewVMSize "Standard_E4s_v3"
 
 
.EXAMPLE
# Move VM to Azure Availability Set
# If Av Set doesn't exist, you can create it like this:
 
# If Av Set doesn't exist, you can create it like this:
$Location = "westeurope"
$AzureAvailabilitySetName = "sap-app-servers-zone1"
$VMResourceGroupName = "gor-Zone-Migration"
 
$ProximityPlacementGroupName = "PPGZone1"
$PPGResourceGroupName "MyPPGResourceGroupName"
$PlatformFaultDomainCount = 3
$PlatformUpdateDomainCount = 2
 
$PPG = Get-AzProximityPlacementGroup -ResourceGroupName $PPGResourceGroupName -Name $ProximityPlacementGroupName
New-AzAvailabilitySet -Location $Location -Name $AzureAvailabilitySetName -ResourceGroupName $VMResourceGroupName -PlatformFaultDomainCount $PlatformFaultDomainCount -PlatformUpdateDomainCount $PlatformUpdateDomainCount -ProximityPlacementGroupId $PPG.Id -Sku Aligned
 
     
.EXAMPLE
# Move SAP application server VM to Azure Availability Set and PPG
 
# If Av Set doesn't exist, you can create it like this:
 
# If Av Set doesn't exist, you can create it like this:
$Location = "westeurope"
$AzureAvailabilitySetName = "sap-app-servers-zone1"
$VMResourceGroupName = "gor-Zone-Migration"
 
$ProximityPlacementGroupName = "PPGZone1"
$PPGResourceGroupName "MyPPGResourceGroupName"
$PlatformFaultDomainCount = 3
$PlatformUpdateDomainCount = 2
 
$PPG = Get-AzProximityPlacementGroup -ResourceGroupName $PPGResourceGroupName -Name $ProximityPlacementGroupName
New-AzAvailabilitySet -Location $Location -Name $AzureAvailabilitySetName -ResourceGroupName $VMResourceGroupName -PlatformFaultDomainCount $PlatformFaultDomainCount -PlatformUpdateDomainCount $PlatformUpdateDomainCount -ProximityPlacementGroupId $PPG.Id -Sku Aligned
 
 
# Option 1 - you will be asked for confirmation for stopping VM and removing the VM definitition
# This will move SAP application server VM to new Availability Set and PPG
$VMName = "sap-as-3"
Move-AzVMToAvailabilitySetAndOrProximityPlacementGroup -VMResourceGroupName $VMResourceGroupName -VirtualMachineName $VMName -AvailabilitySetName $AzureAvailabilitySetName -PPGResourceGroupName $PPGResourceGroupName -ProximityPlacementGroupName $ProximityPlacementGroupName
 
# Option 2 - you will NOT be asked for confirmation for stopping VM and removing the VM definitition
# This will move SAP application server VM to new Availability Set and PPG
Move-AzVMToAvailabilitySetAndOrProximityPlacementGroup -VMResourceGroupName $VMResourceGroupName -VirtualMachineName $VMName -AvailabilitySetName $AzureAvailabilitySetName -PPGResourceGroupName $PPGResourceGroupName -ProximityPlacementGroupName $ProximityPlacementGroupName -Force
 
.LINK
     
 
.NOTES
    v0.1 - Initial version
 
#>


#Requires -Modules Az.Compute
#Requires -Version 5.1

    
        [CmdletBinding()]
        param(
            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $VMResourceGroupName,
                  
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $VirtualMachineName,
                                   
            [Parameter(Mandatory=$False)]
            [string] $AvailabilitySetName,

            [Parameter(Mandatory=$False)]
            [string] $PPGResourceGroupName,
    
            [Parameter(Mandatory=$False)]
            [string] $ProximityPlacementGroupName,
    
            [switch] $DoNotCopyTags,

            [Parameter(Mandatory=$False)]
            [string] $NewVMSize,

            [switch] $KeepSameDiskNames,

            [switch] $Force
        )
    
        BEGIN{
            $AvailabilitySetExist = $False
            $ProximityPlacementGroupExist = $False    
        }
        
        PROCESS{
            try{                                             

               if(($AvailabilitySetName -eq "") -and ($ProximityPlacementGroupName -eq "")){
                    Write-Error "Availability Set Name and Proximity Placement Group Name are not specified. You need to specify at least one of the parameters." 
                    return
                }
    
                # Proximity Placement Group must exist
                if (($ProximityPlacementGroupName -ne "") -and ($PPGResourceGroupName -ne "")){           
                    $ppg = Get-AzProximityPlacementGroup -ResourceGroupName $PPGResourceGroupName -Name $ProximityPlacementGroupName -ErrorAction Stop

                    $ProximityPlacementGroupExist = $True
                    
                    Write-WithTime_Using_WriteHost "Proximity Placement Group '$ProximityPlacementGroupName' in resource group '$PPGResourceGroupName' exist." #-PrependEmptyLine
                }else{
                    if (($ProximityPlacementGroupName -ne "") -and ($PPGResourceGroupName -eq "")) {                        
                        Throw "Only '-ProximityPlacementGroupName' PowerShell parameter is specified. Please specify also parameter '-PPGResourceGroupName'." 
                    }elseif (($ProximityPlacementGroupName -eq "") -and ($PPGResourceGroupName -ne "")) {                        
                        Throw "Only '-PPGResourceGroupName' PowerShell parameter is specified. Please specify also parameter '-ProximityPlacementGroupName'." 
                    }else{                        
                        Write-WithTime_Using_WriteHost "Proximity Placement Group is not specified." #-PrependEmptyLine
                    }                    
                }

                # Availabity Set Must exist
                if ($AvailabilitySetName -ne ""){           
                    $AvailabilitySet = Get-AzAvailabilitySet -ResourceGroupName $VMResourceGroupName -Name $AvailabilitySetName -ErrorAction Stop
                    $AvailabilitySetExist = $True    
                                        
                    Write-WithTime_Using_WriteHost "Availability Set '$AvailabilitySetName' in Azure resource group '$VMResourceGroupName' exist." #-PrependEmptyLine

                    # Check if Av Set is in the proper PPG
                    if($ProximityPlacementGroupExist){
                        if($AvailabilitySet.ProximityPlacementGroup.id -ne $ppg.Id){                                         
                            Throw "Existing Availability Set '$AvailabilitySetName' is not member of Proximity Placement Group '$ProximityPlacementGroupName'. Please configure Availability Set '$AvailabilitySetName' (go to 'Configuration' tab of '$AvailabilitySetName') to use Proximity Placement Group '$ProximityPlacementGroupName'. "                    
                        }
                        
                        Write-WithTime_Using_WriteHost "Availability Set '$AvailabilitySetName' is configured in appropriate Proximity Placement Group '$ProximityPlacementGroupName'." #-PrependEmptyLine
                    }

                    # Chekc if Availability Set SKU is 'Aligned'
                    if ($AvailabilitySet.Sku -ne "Aligned" ) {                        
                        Throw "Existing Availability Set '$AvailabilitySetName' SKU is '$($AvailabilitySet.Sku)'. Adding the VMs with managed disks to non-managed Availability Set is not supported. Please create an Availability Set with 'Aligned' SKU in order to add a VM with managed disks to it."                        
                    }else{
                        Write-WithTime_Using_WriteHost "Availability Set '$AvailabilitySetName' has appropriate SKU 'Aligned'." # -PrependEmptyLine
                    }
                }else{                                        
                    Write-WithTime_Using_WriteHost "Availability Set is not specified." #-PrependEmptyLine
                }                            
                                
                Write-WithTime_Using_WriteHost  "Starting Virtual Machine '$VirtualMachineName' ..." #-PrependEmptyLine
                $StartVMStatus = Start-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -ErrorAction Stop 
                
                # Get the VM and check existance
                Write-WithTime_Using_WriteHost  "Getting Virtual Machine '$VirtualMachineName' configuration ..." #-PrependEmptyLine
                $originalVM = Get-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -ErrorAction Stop           
                
                [string] $osType             = $originalVM.StorageProfile.OsDisk.OsType
                [string] $location           = $originalVM.Location
                [string] $storageType        = $originalVM.StorageProfile.OsDisk.ManagedDisk.StorageAccountType
                [string] $OSDiskName         = $originalVM.StorageProfile.OsDisk.Name    
                $OSDisk                      = Get-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $OSDiskName    
                $OSDiskTags                  = $OSDisk.Tags                     
                # when non-Zonal disk / VM this value is an empty string
                [string] $VMDiskZone         = $originalVM.Zones   
                $VMPlan                      = $originalVM.Plan
                $VMSecurityType              = $originalVM.SecurityProfile.SecurityType
                [bool] $VMSecureBootEnabled  = $originalVM.SecurityProfile.UefiSettings.SecureBootEnabled
                [bool] $VMVTpmEnabled        = $originalVM.SecurityProfile.UefiSettings.VTpmEnabled
                $VMLicenseType               = $originalVM.LicenseType

                Write-WithTime_Using_WriteHost "Checking Hyper-V Generation of virtual machine '$VirtualMachineName' in resource group '$VMResourceGroupName' ..." #-PrependEmptyLine
                $VMStatus = Get-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -Status
                $GenType = $VMStatus.HyperVGeneration
                if ($GenType -eq "V1") {
                        Write-WithTime_Using_WriteHost "Hyper-V Generation of virtual machine '$VirtualMachineName' in resource group '$VMResourceGroupName' is 'V1' - VM is Generation 1." #-PrependEmptyLine
                    }elseif ($GenType -eq "V2") {
                        Write-WithTime_Using_WriteHost "Hyper-V Generation of virtual machine '$VirtualMachineName' in resource group '$VMResourceGroupName' is 'V2' - VM is Generation 2." #-PrependEmptyLine
                }

                if ($VMSecurityType) {
                    Write-WithTime_Using_WriteHost "VM security type is '$VMSecurityType'." #-PrependEmptyLine
                    Write-WithTime_Using_WriteHost "VM secure boot is set to '$VMSecureBootEnabled'." #-PrependEmptyLine
                    Write-WithTime_Using_WriteHost "VM vTPM (Trusted Platform Module) is set to '$VMVTpmEnabled'." #-PrependEmptyLine
                }else {
                    Write-WithTime_Using_WriteHost "VM security type is 'Standard'." #-PrependEmptyLine
                }

                # Premium V2 disks is not suported
                Write-WithTime_Using_WriteHost  "Checking if virtual machine '$VirtualMachineName' has Azure Premium V2 data disks ... "
                $VMHasAzurePremiumV2Disks = Test-AzVMHasPremiumV2Disks -ResourceGroupName  $VMResourceGroupName -VMName $VirtualMachineName
                if ($VMHasAzurePremiumV2Disks) {                    
                    Throw "VM '$VirtualMachineName' has Azure Azure Premium V2 data disks. It is not possible to convert zonal Azure Premium V2 data disks to regional disks, because Premium V2 are not supported as regional disks."
                }else {                            
                        Write-WithTime_Using_WriteHost  "Virtual machine '$VirtualMachineName' has no Azure Premium V2 data disks."
                }

                 # Ultra disks is not suported
                 Write-WithTime_Using_WriteHost  "Checking if virtual machine '$VirtualMachineName' has Azure Ultra data disks ... "
                 $VMHasAzureUltraDisks = Test-AzVMHasUltraDisks -ResourceGroupName  $VMResourceGroupName -VMName $VirtualMachineName
                 if ($VMHasAzureUltraDisks) {                     
                     Throw "VM '$VirtualMachineName' has Azure Azure Ultra data disks. Azure Ultra data disks are not supported."
                 }else {                            
                         Write-WithTime_Using_WriteHost  "Virtual machine '$VirtualMachineName' has no Azure Ultra data disks."
                 }

                # Azure shared disks are not supported
                Write-WithTime_Using_WriteHost  "Checking if virtual machine '$VirtualMachineName' has Azure shared data disks ... "
                $VMHasAzureSharedDisks = Test-AzVMHasAzureSharedDisks -ResourceGroupName  $VMResourceGroupName -VMName $VirtualMachineName
                if ($VMHasAzureSharedDisks) {
                    Throw "VM '$VirtualMachineName' has Azure shared disk. Azure shared disks are not supported."
                }else {                            
                    Write-WithTime_Using_WriteHost  "Virtual machine '$VirtualMachineName' has no Azure shared data disks."
                }

                $IsVMZonal = Test-AzVMIsZonalVM -ResourceGroupName $VMResourceGroupName -VirtualMachineName $VirtualMachineName
                
                # Do not delete OS and Data disks during the VM deletion - set DeleteOption to 'Detach'
                Set-AzVMDisksDeleteOption -VM $originalVM -DeleteOption "Detach" -ErrorAction Stop                              
    
                # Do not delete NIC cards during the VM deletion - Set NIC Cards to 'Detach'
                Set-AzVMNICsDeleteOption -VM $originalVM -DeleteOption "Detach" -ErrorAction Stop             
                
                # Shutdown the original VM
                $ToStop = $true

                if(-not $Force){                    
                    $ToStop = Get-AzVMStopAnswer -VirtualMachineName $VirtualMachineName -ResourceGroupName $VMResourceGroupName
                }                                        

                if($ToStop){                    
                    Write-WithTime_Using_WriteHost  "Stopping Virtual Machine '$VirtualMachineName' in resource group '$VMResourceGroupName' ..." #-AppendEmptyLine
                    $ReturnStopVM =  Stop-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -force -ErrorAction Stop 

                    #Write-WithTime_Using_WriteHost "Stopping Virtual Machine '$VirtualMachineName' in resource group '$VMResourceGroupName' stoped."
                }elseif (!$ToStop) {                    
                    Return                 
                }                 
                    
                # Get the VM size
                $OriginalVMSize =  $originalVM.HardwareProfile.VmSize
                if($NewVMSize -eq ""){
                    # if $NewVMSIze is not specified, use the orgonal VM size
                    Write-WithTime_Using_WriteHost "VM type is '$OriginalVMSize'."

                    $VMSize = $OriginalVMSize
                }
                else{
                    # if $NewVMSIze is specified, use it as VM size
                    Write-WithTime_Using_WriteHost "Changing VM type from original '$OriginalVMSize' to new type '$NewVMSize' ..."

                    $VMSize = $NewVMSize
                }               
                
                # Export original VM configuration
                Export-VMConfigurationToJSONFile -VM  $originalVM                          
                
                # We don't support moving machines with public IPs, since those are zone specific.
                foreach ($nic in $originalVM.NetworkProfile.NetworkInterfaces) {
                    $thenic = $nic.id
                    $nicname = $thenic.substring($thenic.LastIndexOf("/")+1)
                    $othernic = Get-AzNetworkInterface -name $nicname -ResourceGroupName $VMResourceGroupName 
        
                    foreach ($ipc in $othernic.IpConfigurations) {
                        $pip = $ipc.PublicIpAddress
                        if ($pip) { 
                            Throw  "Sorry, machines with public IPs are not supported by this script"                             
                        }
                    }
                }                        

                # Create the basic configuration for the replacement VM with PPG + Av Set
                if(($AvailabilitySetExist) -and ($ProximityPlacementGroupExist)){                    
                    Write-WithTime_Using_WriteHost "Configuring Virtual Machine to use Availability Set '$AvailabilitySetName' and Proximity Placement Group '$ProximityPlacementGroupName' ..."
                    $newVM = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VMSize -AvailabilitySetId $AvailabilitySet.Id -ProximityPlacementGroupId $ppg.Id 
                }elseif($AvailabilitySetExist){                    
                    Write-WithTime_Using_WriteHost "Configuring Virtual Machine to use Availability Set '$AvailabilitySetName' ..."
                    $newVM = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VMSize -AvailabilitySetId $AvailabilitySet.Id 
                }elseif($ProximityPlacementGroupExist){                    
                    "Configuring Virtual Machine to use Proximity Placement Group '$ProximityPlacementGroupName' ..."
                    $newVM = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VMSize -ProximityPlacementGroupId $ppg.Id 
                }         
                
                # add VM PLan if exists
                if($VMPlan){                    
                    Write-WithTime_Using_WriteHost "Original VM uses this Plan: Name '$($VMPlan.Name)', Publisher '$($VMPlan.Publisher)', Product '$($VMPlan.Product)', PromotionCode '$($VMPlan.PromotionCode)'."
                    Write-WithTime_Using_WriteHost "Setting the plan to the new VM ..."
                    Set-AzVMPlan -VM $newVM -Name $VMPlan.Name -Publisher $VMPlan.Publisher -Product $VMPlan.Product  > $Null  #-PromotionCode $VMPlan.PromotionCode
                }
                

               if($IsVMZonal){
                    # VM IS ZONAL VM
                    # move ZONAL VM to Non Zonal
                    # Snapshot all of the OS and Data disks, create NEW non-zonal disk from snapshot, and add to the VM
                                        
                    Write-WithTime_Using_WriteHost  "Virtual machine '$VirtualMachineName' in resource group '$VMResourceGroupName' is ZONAL VM."
                    
                    # snap and copy OS disk
                    if($KeepSameDiskNames){
                        # Snap and copy the os disk
                        #$snapshot = New-AzUniqueNameSnapshot -ResourceGroupName $VMResourceGroupName -Location $location -DiskName $originalVM.StorageProfile.OsDisk.Name -SourceDiskResourceId $originalVM.StorageProfile.OsDisk.ManagedDisk.Id
                        $snapshot = New-AzUniqueNameSnapshot -ResourceGroupName $OSDisk.ResourceGroupName -Location $location -Disk $OSDisk

                        # Create / Copy the exsiting OS Disk with new name as non-zonal disk
                        Copy-AzUniqueNameDiskFromSnapshot -ResourceGroupName $VMResourceGroupName -Location $location -Snapshot $snapshot -OriginalDiskName $OSDiskName -StorageType $storageType -VMDiskZone $VMDiskZone -DiskNamePosfix "orig" -Tags $OSDiskTags
                    }                    
                    
                    # Remove the original VM -this is a prerequisit to delete orignial OS and data disks
                    $ToDelete = $true
    
                    if(-not $Force){                        
                        $ToDelete = Get-AzVMDeleteAnswer -VirtualMachineName $VirtualMachineName -ResourceGroupName $VMResourceGroupName
                    }
                    
                    if($ToDelete){                        
                        Write-WithTime_Using_WriteHost  "Removing Virtual Machine '$VirtualMachineName' ..." 
                        Remove-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -force            
                    }else {
                        # Exit
                        Return   
                    }                                                      

                    if($KeepSameDiskNames){
                        # new OS disk is non-zonal

                        # Delete Original OS Disk
                        Write-WithTime_Using_WriteHost  "Removing original OS disk '$osdiskname' ..." #-AppendEmptyLine
                        Remove-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $osdiskname -Force  
                        
                        # Check for PurchasePlan
                        if($OSDisk.PurchasePlan){
                            Write-WithTime_Using_WriteHost "Original OS disk has purchase plan: Name '$($OSDisk.PurchasePlan.Name)', Publisher '$($OSDisk.PurchasePlan.Publisher)', Product '$($OSDisk.PurchasePlan.Product)', PromotionCode '$($OSDisk.PurchasePlan.PromotionCode)'." -AppendEmptyLine
                            $OSDiskPurchasePlan = $OSDisk.PurchasePlan
                            $diskPurchasePlan = New-AzDiskPurchasePlanConfig -Name  $OSDisk.PurchasePlan.Name -Publisher  $OSDisk.PurchasePlan.Publisher -Product  $OSDisk.PurchasePlan.Product  # -PromotionCode $OSDisk.PurchasePlan.PromotionCode
                            $newdiskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $OSDiskTags -PurchasePlan $diskPurchasePlan
                        }else{
                            Write-WithTime_Using_WriteHost "OS disk has NO purchase plan"
                            $newdiskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $OSDiskTags    
                        }
                                        
                        $newdiskName = $osdiskname 
                    
                        Write-WithTime_Using_WriteHost  "Creating OS disk '$newdiskName' from snapshot '$($snapshot.Name)' ..." -AppendEmptyLine
                        $newdisk = New-AzDisk -Disk $newdiskConfig -ResourceGroupName $VMResourceGroupName -DiskName $newdiskName 

                    }else{
                        # Keep the same disk names
                        
                        # Snap and copy the os disk
                        $snapshotcfg =  New-AzSnapshotConfig -Location $location -CreateOption copy -SourceResourceId $originalVM.StorageProfile.OsDisk.ManagedDisk.Id
                        $osdiskname = $originalVM.StorageProfile.OsDisk.Name
                        $snapshotName = $osdiskname + "-snap"
                    
                        #Write-Host
                        Write-WithTime_Using_WriteHost  "Creating OS disk snapshot '$snapshotName' ..." #-AppendEmptyLine
                        $snapshot = New-AzSnapshot -Snapshot $snapshotcfg -SnapshotName $snapshotName -ResourceGroupName $VMResourceGroupName
                        ##$newdiskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $OSDiskTags #-zone $AzureZone

                        # Check for PurchasePlan
                        if($OSDisk.PurchasePlan){
                            Write-WithTime_Using_WriteHost "Original OS disk has purchase plan: Name '$($OSDisk.PurchasePlan.Name)', Publisher '$($OSDisk.PurchasePlan.Publisher)', Product '$($OSDisk.PurchasePlan.Product)', PromotionCode '$($OSDisk.PurchasePlan.PromotionCode)'." -AppendEmptyLine
                            $OSDiskPurchasePlan = $OSDisk.PurchasePlan
                            $diskPurchasePlan = New-AzDiskPurchasePlanConfig -Name  $OSDisk.PurchasePlan.Name -Publisher  $OSDisk.PurchasePlan.Publisher -Product  $OSDisk.PurchasePlan.Product  # -PromotionCode $OSDisk.PurchasePlan.PromotionCode
                            $newdiskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $OSDiskTags -PurchasePlan $diskPurchasePlan                             
                        }else{
                            Write-WithTime_Using_WriteHost "Original OS disk has NO purchase plan" #-AppendEmptyLine
                            $newdiskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $OSDiskTags
                        }
                        
                        $newdiskName = $osdiskname + "-new" #+ $AzureZone
                        Write-WithTime_Using_WriteHost  "Creating regional OS disk '$newdiskName' from snapshot '$snapshotName' ..."
                        $newdisk = New-AzDisk -Disk $newdiskConfig -ResourceGroupName $VMResourceGroupName -DiskName $newdiskName
                    }

                   
            
                    # Configure new REGIONAL OS Disk
                    if ($osType -eq "Linux")
                    {   
                        Write-WithTime_Using_WriteHost "Configuring Linux OS disk '$newdiskName' for Virtual Machine '$VirtualMachineName'... "  
                        Set-AzVMOSDisk -VM $newVM -CreateOption Attach  -ManagedDiskId $newdisk.Id -Name $newdisk.Name  -Linux -Caching $originalVM.StorageProfile.OsDisk.Caching > $null
                    }
                    if ($osType -eq "Windows")
                    {
                        Write-WithTime_Using_WriteHost "Configuring Windows OS disk '$newdiskName' Virtual Machine '$VirtualMachineName' ... " 
                        Set-AzVMOSDisk -VM $newVM -CreateOption Attach  -ManagedDiskId $newdisk.Id -Name $newdisk.Name  -Windows -Caching $originalVM.StorageProfile.OsDisk.Caching > $null    
                    }

                    # Configure Data Disks

                    if($KeepSameDiskNames){
                        # keep the same disk names
                         # Snapshot all of the Data disks, and add to the VM
                         foreach ($disk in $originalVM.StorageProfile.DataDisks){
                                    
                            $OriginalDataDiskName = $disk.Name
                            
                            $DataDisk             = Get-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $OriginalDataDiskName    
                            $DataDiskTags         = $DataDisk.Tags
                            $storageType          = $DataDisk.Sku.Name

                            #snapshot & copy the data disk
                            #$snapshot = New-AzUniqueNameSnapshot -ResourceGroupName $VMResourceGroupName -Location $location -DiskName $disk.Name -SourceDiskResourceId $disk.ManagedDisk.Id
                            $snapshot = New-AzUniqueNameSnapshot -ResourceGroupName $DataDisk.ResourceGroupName -Location $location -Disk $DataDisk
                            
                            $diskName = $disk.Name

                            # Create / Copy the exsiting Data disk with a new name
                            Copy-AzUniqueNameDiskFromSnapshot -ResourceGroupName $VMResourceGroupName -Location $location -Snapshot $snapshot -OriginalDiskName $OriginalDataDiskName -StorageType $storageType -VMDiskZone $VMDiskZone -DiskNamePosfix "orig" -Tags $DataDiskTags

                            # Delete Original Data disk
                            Write-WithTime_Using_WriteHost  "Removing original data disk '$OriginalDataDiskName' ..." 
                            Remove-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $OriginalDataDiskName -Force 
                                                                            
                            $diskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $DataDiskTags #-zone $AzureZone
                            Write-WithTime_Using_WriteHost  "Creating data disk '$diskName' from snapshot '$($snapshot.Name)' ..." -AppendEmptyLine
                            
                            $newdisk = New-AzDisk -Disk $diskConfig -ResourceGroupName $VMResourceGroupName -DiskName $diskName # > $null
                            
                            Write-WithTime_Using_WriteHost "Configuring data disk '$($newdisk.Name)' , LUN '$($disk.Lun)' for Virtual Machine '$VirtualMachineName' ... " 
        
                            if($disk.WriteAcceleratorEnabled) {                                
                                Write-WithTime_Using_WriteHost "Adding disk '$($newdisk.Name)' to new VM with enabled Write Accelerator ... " -AppendEmptyLine
                                Add-AzVMDataDisk -VM $newVM -Name $newdisk.Name -ManagedDiskId $newdisk.Id -Caching $disk.Caching -Lun $disk.Lun -DiskSizeInGB $newdisk.DiskSizeGB -CreateOption Attach -WriteAccelerator  > $null    
                            }else{                                
                                Write-WithTime_Using_WriteHost "Adding disk '$($newdisk.Name)' to new VM ... "  #-PrependEmptyLine -AppendEmptyLine
                                Add-AzVMDataDisk -VM $newVM -Name $newdisk.Name -ManagedDiskId $newdisk.Id -Caching $disk.Caching -Lun $disk.Lun -DiskSizeInGB $newdisk.DiskSizeGB -CreateOption Attach > $null    
                            }
                        }                       
                    }else{
                        # Generate new disk name with "*-new" pattern
                        foreach ($disk in $originalVM.StorageProfile.DataDisks)
                        {
                            #snapshot & copy the data disk
                            $snapshotcfg =  New-AzSnapshotConfig -Location $location -CreateOption copy -SourceResourceId $disk.ManagedDisk.Id
                            $snapshotName = $disk.Name + "-snap"                                    
                            Write-WithTime_Using_WriteHost  "Creating data disk snapshot '$snapshotName' ..."      
                            $snapshot = New-AzSnapshot -Snapshot $snapshotcfg -SnapshotName $snapshotName -ResourceGroupName $VMResourceGroupName
        
                            #[string] $thisdiskStorageType = $disk.StorageAccountType
                            $diskName = $disk.Name + "-new" #+ $AzureZone
                            $OriginalDataDiskName = $disk.Name
                            $DataDisk             = Get-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $OriginalDataDiskName    
                            $DataDiskTags         = $DataDisk.Tags
                            $storageType          = $DataDisk.Sku.Name

                            $diskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $DataDiskTags #-zone $AzureZone
                            Write-WithTime_Using_WriteHost  "Creating regional data disk '$diskName' from snapshot '$snapshotName' ..."
                            
                            $newdisk = New-AzDisk -Disk $diskConfig -ResourceGroupName $VMResourceGroupName -DiskName $diskName # > $null
                            
                            Write-WithTime_Using_WriteHost "Configuring data disk '$($newdisk.Name)' , LUN '$($disk.Lun)' for Virtual Machine '$VirtualMachineName' ... " 
        
                            if($disk.WriteAcceleratorEnabled) {
                                Write-WithTime_Using_WriteHost "Adding disk '$($newdisk.Name)' to new VM with enabled Write Accelerator ... "
                                Add-AzVMDataDisk -VM $newVM -Name $newdisk.Name -ManagedDiskId $newdisk.Id -Caching $disk.Caching -Lun $disk.Lun -DiskSizeInGB $newdisk.DiskSizeGB -CreateOption Attach -WriteAccelerator  > $null    
                            }else{
                                Write-WithTime_Using_WriteHost "Adding disk '$($newdisk.Name)' to new VM ... "
                                Add-AzVMDataDisk -VM $newVM -Name $newdisk.Name -ManagedDiskId $newdisk.Id -Caching $disk.Caching -Lun $disk.Lun -DiskSizeInGB $newdisk.DiskSizeGB -CreateOption Attach > $null    
                            }
                        }                
                    }
                        
               }else{
                    # VM is NON Zonal
                    Write-WithTime_Using_WriteHost  "Virtual machine '$VirtualMachineName' in resource group '$VMResourceGroupName' is NON-ZONAL regional VM."                    

                    # Attach EXISTING disks
                    if ($osType -eq "Linux")
                    {   Write-WithTime_Using_WriteHost "Configuring Linux OS disk '$OSDiskName' .. "                
                        Set-AzVMOSDisk  -VM $newVM -CreateOption Attach -ManagedDiskId $originalVM.StorageProfile.OsDisk.ManagedDisk.Id -Name $OSDiskName -Linux -Caching $originalVM.StorageProfile.OsDisk.Caching > $null
                
                    }elseif ($osType -eq "Windows")
                    {   Write-WithTime_Using_WriteHost "Configuring Windows OS disk '$OSDiskName' .. " 
                        Set-AzVMOSDisk  -VM $newVM -CreateOption Attach -ManagedDiskId $originalVM.StorageProfile.OsDisk.ManagedDisk.Id -Name $OSDiskName -Windows -Caching $originalVM.StorageProfile.OsDisk.Caching    > $null    
                    }
        
                    # Add Data Disks
                    foreach ($disk in $originalVM.StorageProfile.DataDisks) { 
                        Write-WithTime_Using_WriteHost "Adding data disk '$($disk.Name)' to Virtual Machine '$VirtualMachineName' ..."
                        Add-AzVMDataDisk -VM $newVM -Name $disk.Name -ManagedDiskId $disk.ManagedDisk.Id -Caching $disk.Caching -Lun $disk.Lun -DiskSizeInGB $disk.DiskSizeGB -CreateOption Attach > $null
                    }                            
               }                                                    
    
                # Add NIC(s) and keep the same NIC as primary
                foreach ($nic in $originalVM.NetworkProfile.NetworkInterfaces) {                  
                    Write-WithTime_Using_WriteHost "Adding '$($nic.Id)' network card to Virtual Machine '$VirtualMachineName' ..."
                    if ($nic.Primary -eq "True"){                
                    Add-AzVMNetworkInterface -VM $newVM -Id $nic.Id -Primary > $null
                        }
                        else{                
                        Add-AzVMNetworkInterface -VM $newVM -Id $nic.Id > $null
                    }
                }
                    
                if(-not $DoNotCopyTags){                    
                    # Copy the VM Tags
                    Write-WithTime_Using_WriteHost "Copy Tags ..."
                    $newVM.Tags = $originalVM.Tags
                    
                    Write-WithTime_Using_WriteHost "Tags copy to new VM definition done. "
                }else{
                    Write-Host "Skipping copy of VM tags:"                            
                }
            
                # Configuring Boot Diagnostics
                if ($originalVM.DiagnosticsProfile.BootDiagnostics.Enabled) {
        
                    Write-WithTime_Using_WriteHost "Boot diagnostic account is enabled."
                    
                    # Get Strage URI
                    $StorageUri = $originalVM.DiagnosticsProfile.BootDiagnostics.StorageUri 
                    
                    if ($StorageUri -eq $null) {
        
                        Write-WithTime_Using_WriteHost "Boot diagnostic URI is empty."
                        Write-WithTime_Using_WriteHost "Configuring boot diganostic with managed storage account ..."
                        $newVM = Set-AzVMBootDiagnostic -VM $newVM -Enable                      
                    }else {
                        
                        $BootDiagnosticURI = $originalVM.DiagnosticsProfile.BootDiagnostics.StorageUri.Split("/")[2]
                        
                        Write-WithTime_Using_WriteHost "Boot diagnostic URI: '$BootDiagnosticURI'."
            
                        $staccName = $BootDiagnosticURI.Split(".")[0]
                        Write-WithTime_Using_WriteHost "Extracted storage account name: '$staccName'"
            
                        Write-WithTime_Using_WriteHost "Getting storage account '$staccName'"
                        $stacc = Get-AzStorageAccount | where-object { $_.StorageAccountName.Contains($staccName) }
                        
                        if($stacc  -eq $null ){
                            Write-WithTime_Using_WriteHost "Storage account '$staccName' used for diagonstic account on source VM do not exist." #-PrependEmptyLine -AppendEmptyLine
                                
                            Write-WithTime_Using_WriteHost "Configuring boot diganostic with managed storage account ..." # -AppendEmptyLine

                            $newVM = Set-AzVMBootDiagnostic -VM $newVM -Enable       
                        
                        }else{
                            Write-WithTime_Using_WriteHost "Configuring storage account '$staccName' for VM boot diagnostigs in Azure resource group '$($stacc.ResourceGroupName)' on the new VM ..."
                        
                            $newVM = Set-AzVMBootDiagnostic -VM $newVM -Enable -ResourceGroupName $stacc.ResourceGroupName -StorageAccountName $staccName
        
                            Write-WithTime_Using_WriteHost "Configuring storage account '$staccName' for VM boot diagnostigs done."    
                        }                        
                    }                    
            }
    
            if(-not $IsVMZonal){
                # Remove the original VM
                $ToDelete = $true

                if(-not $Force){                    
                    $ToDelete = Get-AzVMDeleteAnswer -VirtualMachineName $VirtualMachineName -ResourceGroupName $VMResourceGroupName
                }
                
                if($ToDelete){                    
                    Write-WithTime_Using_WriteHost  "Removing Virtual Machine '$VirtualMachineName' definition ..."                    
                    Remove-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -force            
                }else {                
                    # Exit
                    Return                   
                }
            }            
    
            if ($VMSecurityType -eq "TrustedLaunch") {
                Write-WithTime_Using_WriteHost "Configuring VM security setting to 'TrustedLaunch' ..."
                $NewVM = Set-AzVmSecurityProfile -VM $NewVM -SecurityType "TrustedLaunch"

                Write-WithTime_Using_WriteHost "Configuring VM secure boot '$VMSecureBootEnabled' and vTPM to '$VMVTpmEnabled' ..."
                $NewVM = Set-AzVMUefi -VM $newVM -EnableSecureBoot $VMSecureBootEnabled -EnableVtpm $VMVTpmEnabled
            }
            
            if($IsVMZonal){
                Write-WithTime_Using_WriteHost "Original VM was a ZONAL VM. Recreating Virtual Machine '$VirtualMachineName' in resource group '$VMResourceGroupName' as regional VM ..."
            }else{
                Write-WithTime_Using_WriteHost "Recreating Virtual Machine '$VirtualMachineName' in resource group '$VMResourceGroupName' ..."
            }

            
            # recreate the VM
            if ($VMLicenseType) {
                New-AzVM -ResourceGroupName $VMResourceGroupName -Location $originalVM.Location -VM $newVM -DisableBginfoExtension -LicenseType $VMLicenseType
            }else {
                New-AzVM -ResourceGroupName $VMResourceGroupName -Location $originalVM.Location -VM $newVM -DisableBginfoExtension 
            }
        
            Write-WithTime_Using_WriteHost "Done!"
            
            }
            catch{
               Write-Error  $_.Exception.Message           
           }
        }
    
        END {}
    }
    
    function Export-VMConfigurationToJSONFile {
        [CmdletBinding()]
        param(
            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            $VM                            
        )
    
        BEGIN{}
        
        PROCESS{
            try{   
               
               $VMName = $VM.Name
               $FileName = "$VMName.json"
    
               $VM | ConvertTo-Json -depth 100 | Out-File $FileName
    
               Write-WithTime_Using_WriteHost "Virtual Machine '$VMName' configuration is exported to file '$FileName' "
    
            }
            catch{
               Write-Error  $_.Exception.Message -ErrorAction Stop 
           }
    
        }
    
        END {}
    }
    
    function Move-AzVMToAzureZoneAndOrProximityPlacementGroup {
    <#
    .SYNOPSIS
        Moves a VM into an Azure availability zone, or move VM from one Azure zone to another Azure zone
     
    .DESCRIPTION
        The script deletes the VM and recreates it preserving networking and storage configuration. The script will snapshot each disk, create a new Zonal disk from the snapshot, and create the new Zonal VM with the new disks attached.
        New zonal disk names will have this naming convention: <OriginalDiskName>-z<ZoneNumber>. If you want to keep the same disk names, you need to use flag KeepSameDiskNames (see below for more information)
        Disk type are Standard or Premium managed disks.
     
        There is no need to reinstall the operating system.
         
        IMPORTANT: The script does not preserve VM extensions. Also, the script will not work for VMs with public IP addresses.
         
        If you specify -ProximityPlacementGroupName parameter, VM will be added to the Proximity Placement Group. Proximity Placement Group must exist.
     
        IMPORTANT: In case that there are other VMs that are part of Proximity Placement Group and the desired Zone, make sure that desired zone and PPG is the same zone where existing VMs are placed!
     
        If your VM is part of an Azure Internal Load Balancer (ILB),specify the name of Azure ILB by using -AzureInternalLoadBalancerName parameter.
     
        IMPORTANT: Script will check that Azure ILB is of Standard SKU Type, which is needed for the Zones.
                   If Azure ILB is of type 'Basic', first you need to convert existing ILB to 'Standard' SKU Type.
         
        IMPORTANT: SAP High Availability context
     
        In SAP High Availability context, script is aplicable when moving for example clustered SAP ASCS/SCS cluster VMs, or DBMS cluster VMs from an Availability Set with Standard Azure ILB, to Azure Zone with Standard Azure ILB.
     
        If you want to add the VM to Proximity Placement Group, expectation is that:
           - Proximity Placement Group alreday exist
           - First ancor VM (this is DBMS VM) is alreday deplyed in a Zone and same Proximity Placement Group
     
            
    .PARAMETER VMResourceGroupName
    Resource Group Name of the VM.
         
    .PARAMETER VirtualMachineName
    Virtual Machine Name name.
     
    .PARAMETER AzureZone
    Azure Zone number.
     
    .PARAMETER PPGResourceGroupName
    Resource group name of the Proximity Placement Group
 
    .PARAMETER ProximityPlacementGroupName
    Proximity Placement Group Name
     
    .PARAMETER AzureInternalLoadBalancerResourceGroupName
    Resource group name of the Internal Load Balancer.
     
 
    .PARAMETER AzureInternalLoadBalancerName
    Azure Internal Load Balancer Name
     
    .PARAMETER DoNotCopyTags
    Switch paramater. If specified, VM tags will NOT be copied.
 
    .PARAMETER NewVMSize
    If NewVMSize is specified , VM will be set to a new VM size. Otherwise, original VM size will be used.
 
    .PARAMETER KeepSameDiskNames
    If new ‚KeepSameDiskNames‘ flag is specified, OS and data disks names will stay the same. For each disk new unique snapshot will be created, and from snapshot will be created a copy of the original disk with unique disk name (<originalDiskName>-orig<nr> ) .
    These disk copies can be used to restore original VM. Original disk will then be deleted, and new disk in the desired zone will be created from snapshot, using the original disk names.
 
     
    .PARAMETER Force
    Forces the command to run without asking for user confirmation.
     
    .EXAMPLE
        # Move VM 'VM1' to Azure Zone '2'
        # VM tags will not be copied : swicth parameter -DoNotCopyTags is set
     
        Move-AzVMToAzureZoneAndOrProximityPlacementGroup -VMResourceGroupName SAP-SB1-ResourceGroup -VirtualMachineName VM1 -AzureZone 2 -DoNotCopyTags
     
    .EXAMPLE
        # Move VM 'VM1' to Azure Zone '2', and add to exisiting 'PPGForZone2'
        # VM will be set to NEW VM size, e.g. not original VM size , because 'NewVMSize' is specified
        Move-AzVMToAzureZoneAndOrProximityPlacementGroup -VMResourceGroupName SAP-SB1-ResourceGroup -VirtualMachineName VM1 -AzureZone 2 -PPGResourceGroupName ppg-group -ProximityPlacementGroupName PPGForZone2 -NewVMSize "Standard_E4s_v3"
         
     
    .EXAMPLE
        # This scenario is used to move higly available DB cluster nodes, SAP ASCS/SCS clsuter nodes, or file share cluster nodes
        # Move VM 'VM1' to Azure Zone '2', and add to exisiting 'PPGForZone2' , and check if Azure Internal Load Balancer 'SB1-ASCS-ILB' has 'Standard' SKU type
        # User is asked for confirmation to stop the VM and delete the VM
        Move-AzVMToAzureZoneAndOrProximityPlacementGroup -VMResourceGroupName SAP-SB1-ResourceGroup -VirtualMachineName sb1-ascs-cl1 -AzureZone 2 -PPGResourceGroupName ppg-group -ProximityPlacementGroupName PPGForZone2 -AzureInternalLoadBalancerResourceGroupName gor-lb-group -AzureInternalLoadBalancerName SB1-ASCS-ILB
     
    .EXAMPLE
        # This scenario is used to move higly available DB cluster nodes, SAP ASCS/SCS clsuter nodes, or file share cluster nodes
        # Move VM 'VM1' to Azure Zone '2', and add to exisiting 'PPGForZone2' , and check if Azure Internal Load Balancer 'SB1-ASCS-ILB' has 'Standard' SKU type
        # User is NOT asked for confirmation to stop the VM and delete the VM
        Move-AzVMToAzureZoneAndOrProximityPlacementGroup -VMResourceGroupName SAP-SB1-ResourceGroup -VirtualMachineName sb1-ascs-cl1 -AzureZone 2 -PPGResourceGroupName ppg-group -ProximityPlacementGroupName PPGForZone2 -AzureInternalLoadBalancerResourceGroupName gor-lb-group -AzureInternalLoadBalancerName SB1-ASCS-ILB -Force
     
 
    .LINK
         
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
        [CmdletBinding()]
        param(
            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $VMResourceGroupName,
                  
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $VirtualMachineName,
    
            [Parameter(Mandatory=$True)]
            [string] $AzureZone,
    
            [Parameter(Mandatory=$False)]              
            [string] $PPGResourceGroupName,

            [Parameter(Mandatory=$False)]
            [string] $ProximityPlacementGroupName,
    
            [Parameter(Mandatory=$False)]
            [ValidateNotNullOrEmpty()]        
            [string] $AzureInternalLoadBalancerResourceGroupName,

            [Parameter(Mandatory=$False)]
            [string] $AzureInternalLoadBalancerName,
    
            [switch] $DoNotCopyTags,

            [switch] $KeepSameDiskNames,

            [Parameter(Mandatory=$False)]
            [string] $NewVMSize,
            
            [switch] $Force
        )
    
        BEGIN{        
            $ProximityPlacementGroupExist = $False    
        }
        
        PROCESS{
            try{                  
                # Handle Azure Load Balancer
                if (($AzureInternalLoadBalancerName -ne "") -and ($AzureInternalLoadBalancerResourceGroupName -ne "")) {           
                        $ILB = Get-AzLoadBalancer -ResourceGroupName $AzureInternalLoadBalancerResourceGroupName -Name $AzureInternalLoadBalancerName -ErrorAction Stop
        
                        if($ILB -ne $null){
                            $AzureInternalLoadBalancerNameExist = $True
                            #Write-Host
                            Write-WithTime_Using_WriteHost "Azure Internal Load Balancer '$AzureInternalLoadBalancerName' in resource group '$AzureInternalLoadBalancerResourceGroupName' exist."
        
                            #check if ILB SKU for 'Standard'
                            if($ILB.Sku.Name -eq "Standard"){
                                #Write-Host
                                Write-WithTime_Using_WriteHost "Azure Internal Load Balancer '$AzureInternalLoadBalancerName' has expected 'Standard' SKU."
                            }
                            else{
                                Throw  "Specified Azure Internal Load BalancerName is not 'Standard' load balancer. Before proceeding convert '$AzureInternalLoadBalancerName' load balancer from 'Basic' to 'Standard' SKU type." 
                            }
                        }else{
                            Throw  "Specified Azure Internal Load BalancerName '$AzureInternalLoadBalancerName' doesn't exists. Please check your input parameter 'AzureInternalLoadBalancerName' and 'AzureInternalLoadBalancerResourceGroupName'." 
                        }
                                                    
                }else{                    

                        if (($AzureInternalLoadBalancerName -ne "") -and ($AzureInternalLoadBalancerResourceGroupName -eq "")) {
                            #Write-Host
                            Throw "Only '-AzureInternalLoadBalancerName' PowerShell parameter is specified. Please specify also parameter '-AzureInternalLoadBalancerResourceGroupName'." 

                        }elseif (($AzureInternalLoadBalancerName -eq "") -and ($AzureInternalLoadBalancerResourceGroupName -ne "")) {
                            #Write-Host
                            Throw "Only '-AzureInternalLoadBalancerResourceGroupName' PowerShell parameter is specified. Please specify also parameter '-AzureInternalLoadBalancerName'." 
                        }else{
                            #Write-Host
                            Write-WithTime_Using_WriteHost "Azure Internal Load Balancer is not specified."                         
                        }                    
                }  
               
                # Handle Proximity Placement Group
                if (($ProximityPlacementGroupName -ne "") -and ($PPGResourceGroupName -ne "")) {        
                    $ppg = Get-AzProximityPlacementGroup -ResourceGroupName $PPGResourceGroupName -Name $ProximityPlacementGroupName -ErrorAction Stop    
                    $ProximityPlacementGroupExist = $True
                    
                    #Write-Host
                    Write-WithTime_Using_WriteHost "Proximity Placement Group '$ProximityPlacementGroupName' in resource group '$PPGResourceGroupName' exist."    
                    
                    #Write-Host
                    Write-WithTime_Using_WriteHost "Starting migration of Virtual Machine '$VirtualMachineName' to Azure Zone '$AzureZone', and Proximity Placement Group '$ProximityPlacementGroupName' ..."                                   
                }else {
                    if (($ProximityPlacementGroupName -ne "") -and ($PPGResourceGroupName -eq "")) {
                        #Write-Host
                        Throw "Only '-ProximityPlacementGroupName' PowerShell parameter is specified. Please specify also parameter '-PPGResourceGroupName'." 

                    }elseif (($ProximityPlacementGroupName -eq "") -and ($PPGResourceGroupName -ne "")) {
                        #Write-Host
                        Throw "Only '-PPGResourceGroupName' PowerShell parameter is specified. Please specify also parameter '-ProximityPlacementGroupName'." 
                    }else{
                        #Write-Host
                        Write-WithTime_Using_WriteHost "Proximity Placement Group is not specified."

                        #Write-Host
                        Write-WithTime_Using_WriteHost "Starting migration of Virtual Machine '$VirtualMachineName' to Azure Zone '$AzureZone' ..."
                    }     
                }

               #Write-Host
               Write-WithTime_Using_WriteHost  "Starting virtual machine '$VirtualMachineName' ..."
               Start-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -ErrorAction Stop 
    
               # get VM and check existance
               $originalVM = Get-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -ErrorAction Stop   
               
               # Premium V2 disks is not suported
            # Write-WithTime_Using_WriteHost "Checking if virtual machine '$VirtualMachineName' has Azure Premium V2 data disks ... "
            # $VMHasAzurePremiumV2Disks = Test-AzVMHasPremiumV2Disks -ResourceGroupName $VMResourceGroupName -VMName $VirtualMachineName
            # if ($VMHasAzurePremiumV2Disks) {
            # #Write-Host
            # Throw "VM '$VirtualMachineName' has Azure Azure Premium V2 data disks. Azure Premium V2 data disks are not supported."
            # }else {
            # Write-WithTime_Using_WriteHost "Virtual machine '$VirtualMachineName' has no Azure Premium V2 data disks."
            # }

                # Ultra disks is not suported
                Write-WithTime_Using_WriteHost  "Checking if virtual machine '$VirtualMachineName' has Azure Ultra data disks ... "
                $VMHasAzureUltraDisks = Test-AzVMHasUltraDisks -ResourceGroupName  $VMResourceGroupName -VMName $VirtualMachineName
                if ($VMHasAzureUltraDisks) {
                    #Write-Host
                    Throw "VM '$VirtualMachineName' has Azure Azure Ultra data disks. Azure Ultra data disks are not supported."
                }else {                            
                        Write-WithTime_Using_WriteHost  "Virtual machine '$VirtualMachineName' has no Azure Ultra data disks."
                }
               
               # Azure shared disks are not supported
               #Write-Host
               Write-WithTime_Using_WriteHost  "Checking if virtual machine '$VirtualMachineName' has Azure shared data disks ... "
               $VMHasAzureSharedDisks = Test-AzVMHasAzureSharedDisks -ResourceGroupName  $VMResourceGroupName -VMName $VirtualMachineName
               if ($VMHasAzureSharedDisks) {
                    Write-Host   
                    Throw "VM '$VirtualMachineName' has Azure shared disk. Azure shared disks are not supported."
               }else {
                    #Write-Host
                    Write-WithTime_Using_WriteHost  "Virtual machine '$VirtualMachineName' has no Azure shared data disks."
               }
                                                                                                                                    
               # We don't support moving machines with public IPs, since those are zone specific.
               foreach ($nic in $originalVM.NetworkProfile.NetworkInterfaces) {
                     $thenic = $nic.id
                     $nicname = $thenic.substring($thenic.LastIndexOf("/")+1)
                     $othernic = Get-AzNetworkInterface -name $nicname -ResourceGroupName $VMResourceGroupName 
                     #Write-Host
                     Write-WithTime_Using_WriteHost "Found Network Card '$nicname' in Azure resource group '$VMResourceGroupName'."
            
                     foreach ($ipc in $othernic.IpConfigurations) {
                         $pip = $ipc.PublicIpAddress
                         if ($pip) { 
                             Throw  "Sorry, machines with public IPs are not supported by this script" 
                                #exit
                         }
                     }
               }
             
               [string] $osType             = $originalVM.StorageProfile.OsDisk.OsType
               [string] $location           = $originalVM.Location
               [string] $storageType        = $originalVM.StorageProfile.OsDisk.ManagedDisk.StorageAccountType
               [string] $OSDiskName         = $originalVM.StorageProfile.OsDisk.Name
               # when non-Zonal disk / VM this value is an empty string
               [string] $VMDiskZone         = $originalVM.Zones
               $OSDisk                      = Get-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $OSDiskName    
               $OSDiskTags                  = $OSDisk.Tags  
               $VMPlan                      = $originalVM.Plan
               $VMSecurityType              = $originalVM.SecurityProfile.SecurityType
               [bool] $VMSecureBootEnabled  = $originalVM.SecurityProfile.UefiSettings.SecureBootEnabled
               [bool] $VMVTpmEnabled        = $originalVM.SecurityProfile.UefiSettings.VTpmEnabled
                # $VMSecureBootEnabled = $originalVM.SecurityProfile.UefiSettings.SecureBootEnabled
                # $VMVTpmEnabled = $originalVM.SecurityProfile.UefiSettings.VTpmEnabled
               $VMLicenseType               = $originalVM.LicenseType

                # if ($VMDiskZone -eq "") {
                # [bool] $VMIsZonal = $False
                # }else {
                # [bool] $VMIsZonal = $True
                # }

                Write-WithTime_Using_WriteHost  "Checking if Virtual Machine '$VirtualMachineName' is zonal VM ..."
                $VMIsZonal = Test-AzVMIsZonalVM -ResourceGroupName $VMResourceGroupName  -VirtualMachineName $VirtualMachineName

                if ($VMIsZonal) {
                    if ($AzureZone -eq  $originalVM.Zones[0]) {
                        $TargetZoneIsSameAsSourceZone = $true 
                        Write-WithTime_Using_WriteHost "Target zone '$AzureZone' is the same as the source VM zone. No need to migrate the disks to another zone."   
                    }else {
                        $TargetZoneIsSameAsSourceZone = $False
                        Write-WithTime_Using_WriteHost "Target zone '$AzureZone' is different to the source VM zone '$($originalVM.Zones[0])'. Need to migrate disks to new zone '$AzureZone'."  
                    }
                }
                    
                Write-WithTime_Using_WriteHost "Checking Hyper-V Generation of virtual machine '$VirtualMachineName' in resource group '$VMResourceGroupName' ..." # -PrependEmptyLine
                $VMStatus = Get-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -Status
                $GenType = $VMStatus.HyperVGeneration
                if ($GenType -eq "V1") {
                        Write-WithTime_Using_WriteHost "Hyper-V Generation of virtual machine '$VirtualMachineName' in resource group '$VMResourceGroupName' is 'V1' - VM is Generation 1." #-PrependEmptyLine
                    }elseif ($GenType -eq "V2") {
                        Write-WithTime_Using_WriteHost "Hyper-V Generation of virtual machine '$VirtualMachineName' in resource group '$VMResourceGroupName' is 'V2' - VM is Generation 2." #-PrependEmptyLine
                }

                if ($VMSecurityType) {
                    Write-WithTime_Using_WriteHost "VM security type is '$VMSecurityType'." #-PrependEmptyLine
                    Write-WithTime_Using_WriteHost "VM secure boot is set to '$VMSecureBootEnabled'." #-PrependEmptyLine
                    Write-WithTime_Using_WriteHost "VM vTPM (Trusted Platform Module) is set to '$VMVTpmEnabled'." #-PrependEmptyLine
                }else {
                    Write-WithTime_Using_WriteHost "VM security type is 'Standard'." #-PrependEmptyLine
                }

               # Do not delete OS and Data disks during the VM deletion - set DeleteOption to 'Detach'
               Set-AzVMDisksDeleteOption -VM $originalVM -DeleteOption "Detach" -ErrorAction Stop                               

               # Do not delete NIC cards during the VM deletion - Set NIC Cards to 'Detach'
               Set-AzVMNICsDeleteOption -VM $originalVM -DeleteOption "Detach" -ErrorAction Stop             
                               
               $OriginalVMSize =  $originalVM.HardwareProfile.VmSize

               if($NewVMSize -eq ""){
                    # if $NewVMSIze is not specified, use the original VM size
                    #Write-Host
                    Write-WithTime_Using_WriteHost "VM type is '$OriginalVMSize'."

                    $VMSize = $OriginalVMSize                    
                }
                else{
                    # if $NewVMSIze is specified, use it as VM size
                     
                    #Write-Host
                    Write-WithTime_Using_WriteHost "Changing VM type from original '$OriginalVMSize' to new type '$NewVMSize'."

                    $VMSize = $NewVMSize
                }
        
                # Check if VM SKU is available in the desired Azure zone
                #Write-Host
                Write-WithTime_Using_WriteHost "Checking VM SKU '$VMSize' availablity in Azure zone '$AzureZone' in region '$location' ...."

                $VMSKUIsAvailableinAzureZone = Test-AzComputeSKUZonesAvailability -Location $location  -VMSKU $VMSize -AzureZone $AzureZone
                if (-not $VMSKUIsAvailableinAzureZone) {
                    $AzureZones = Get-AzComputeSKUZonesAvailability -Location $location -VMSKU $VMSize
                    if ($null -eq $AzureZones) {
                        #Write-Host
                        Throw "VM SKU '$VMSize' is not available in any of the Azure zones in region '$location'. PLease use another VM SKU."                            
                    }else {
                        #Write-Host
                        Write-WithTime_Using_WriteHost "VM SKU '$VMSize' is available in these Azure zone(s):"
                        Write-Host $AzureZones
                        #Write-Host
                        Throw "VM SKU '$VMSize' is not available in desired Azure zone '$AzureZone' in region '$location'. PLease use another VM SKU or another zone."    
                    }
                    
                }              

                # if PPG is specified and contains VMs, check:
                 ## if region of the PPG is the same as the region of the VM
                 ## if VMs in the PPG are in the the same zone as target zone of the migrated VM

                if ($ProximityPlacementGroupExist) {
                    
                    # Check if region of the PPG is the same as the region of the VM
                    $PPGLocation = $ppg.Location 
                    if ( $location -ne $PPGLocation) {
                        #Write-Host
                        Throw "Azure region '$location' of the VM '$VirtualMachineName' is different from the Proximity Placement Group '$ProximityPlacementGroupName' region '$PPGLocation'. Choose Proximity Placement Group located in the same region '$location' as the VM '$VirtualMachineName'. "
                    }else {
                        #Write-Host
                        Write-WithTime_Using_WriteHost "VM and Proximity Placement Group are located in the same Azure region '$location'."
                    }

                    # Check if VMs in the PPG are in the the same zone as target zone of the migrated VM
                    $VMsInPPG = Get-AzVMBelongingProximityPlacementGroup -ResourceGroupName $PPGResourceGroupName -PPGName $ProximityPlacementGroupName 

                    if ($null -ne $VMsInPPG) {
                        #Write-Host
                        Write-WithTime_Using_WriteHost "Found VMs in the Proximity Placement Group '$PPGResourceGroupName'."                        

                        foreach ($VMInPPG in $VMsInPPG) {
                            
                            if ($null -ne $VMInPPG.VMZone) {
                                #Write-Host
                                Write-WithTime_Using_WriteHost "VM '$($VMInPPG.VMName)' (anchor VM) asociated with Proximity Placement Group '$ProximityPlacementGroupName' is in Azure zone $($VMInPPG.VMZone)."

                                if ($AzureZone -ne $VMInPPG.VMZone ) {
                                    #Write-Host
                                    Throw "Target VM '$VirtualMachineName' zone '$AzureZone' is different from zone '$($VMInPPG.VMZone)' of the anchor VM '$($VMInPPG.VMName)' belonging to Proximity Placement Group '$ProximityPlacementGroupName'. Chose Proximity Placement Group with VM(s) belonging to the target zone '$AzureZone'."
                                }else {
                                    #Write-Host
                                    Write-WithTime_Using_WriteHost "Target VM '$VirtualMachineName' zone is zone '$AzureZone', and is the same as the zone '$($VMInPPG.VMZone)' of the anchor VM '$($VMInPPG.VMName)' belonging to Proximity Placement Group '$ProximityPlacementGroupName'. "
                                    break
                                }

                            }
                        }

                    }else {
                        #Write-Host
                        Write-WithTime_Using_WriteHost "No (anchor) VMs found in the Proximity Placement Group '$ProximityPlacementGroupName'."
                    }
                }
                
               # Shutdown the original VM
               $ToStop = $true               

               if(-not $Force){
                #Write-Host
                $ToStop = Get-AzVMStopAnswer -VirtualMachineName $VirtualMachineName -ResourceGroupName $VMResourceGroupName
               }
                              
               if($ToStop){
                    #Write-Host
                    Write-WithTime_Using_WriteHost  "Stopping Virtual Machine '$VirtualMachineName' in resource group '$VMResourceGroupName' ..."
                    Stop-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -force -ErrorAction Stop 
               }else {
                    # Exit
                    Return   
               }
               
    
               # Export original VM configuration
               #Write-Host
               Export-VMConfigurationToJSONFile -VM  $originalVM      
                          
               # Create the basic configuration for the replacement VM with Zone and / or PPG
               if($ProximityPlacementGroupExist){
                    #Write-Host
                    Write-WithTime_Using_WriteHost "Configuring Virtual Machine to use Azure Zone '$AzureZone' and Proximity Placement Group '$ProximityPlacementGroupName' ..."
                    $newVM = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VmSize -ProximityPlacementGroupId $ppg.Id -Zone $AzureZone
               }else{
                    #Write-Host
                    Write-WithTime_Using_WriteHost "Configuring Virtual Machine to use Azure Zone '$AzureZone' ..."
                    $newVM = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VmSize -Zone $AzureZone 
               }
                                         
                # add VM PLan if exists
                if($VMPlan){
                    #Write-Host
                    Write-WithTime_Using_WriteHost "Original VM uses this Plan: Name '$($VMPlan.Name)', Publisher '$($VMPlan.Publisher)', Product '$($VMPlan.Product)', PromotionCode '$($VMPlan.PromotionCode)'."
                    #Write-Host
                    Write-WithTime_Using_WriteHost "Setting the plan to the new VM ..."
                    Set-AzVMPlan -VM $newVM -Name $VMPlan.Name -Publisher $VMPlan.Publisher -Product $VMPlan.Product > $Null   #-PromotionCode $VMPlan.PromotionCode
                }

               # Snap and copy the os disk
               #$snapshot = New-AzUniqueNameSnapshot -ResourceGroupName $VMResourceGroupName -Location $location -DiskName $originalVM.StorageProfile.OsDisk.Name -SourceDiskResourceId $originalVM.StorageProfile.OsDisk.ManagedDisk.Id
               if (-not $TargetZoneIsSameAsSourceZone) {
                    $snapshot = New-AzUniqueNameSnapshot -ResourceGroupName $OSDisk.ResourceGroupName -Location $location -Disk $OSDisk  
               }

               if($KeepSameDiskNames -and (-not $TargetZoneIsSameAsSourceZone)){
                    #Write-Host
                    Write-WithTime_Using_WriteHost  "Specified flag 'KeepSameDiskNames'."

                    # Create / Copy the exsiting OS Disk with new name
                    Copy-AzUniqueNameDiskFromSnapshot -ResourceGroupName $VMResourceGroupName -Location $location -Snapshot $snapshot -OriginalDiskName $OSDiskName -StorageType $storageType -VMDiskZone $VMDiskZone -DiskNamePosfix "orig" -Tags $OSDiskTags
                    
                    # Remove the original VM -this is a prerequisit to delete orignial OS and data disks
                    $ToDelete = $true

                    if(-not $Force){
                        Write-Host
                        $ToDelete = Get-AzVMDeleteAnswer -VirtualMachineName $VirtualMachineName -ResourceGroupName $VMResourceGroupName
                    }
                    
                    if($ToDelete){
                        #Write-Host
                        Write-WithTime_Using_WriteHost  "Removing Virtual Machine '$VirtualMachineName' ..."
                        #Write-Host
                        Remove-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -force            
                    }else {
                        # Exit
                        Return   
                    }              

                    # Delete Original OS Disk
                    #Write-Host
                    Write-WithTime_Using_WriteHost  "Removing original OS disk '$osdiskname' ..."
                    Remove-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $osdiskname -Force                                         
               }else {
                   
               }
            
               if($OSDisk.PurchasePlan){
                    Write-WithTime_Using_WriteHost "Original OS disk has purchase plan: Name '$($OSDisk.PurchasePlan.Name)', Publisher '$($OSDisk.PurchasePlan.Publisher)', Product '$($OSDisk.PurchasePlan.Product)', PromotionCode '$($OSDisk.PurchasePlan.PromotionCode)'." -AppendEmptyLine
                    $OSDiskPurchasePlan = $OSDisk.PurchasePlan
                    $diskPurchasePlan = New-AzDiskPurchasePlanConfig -Name  $OSDisk.PurchasePlan.Name -Publisher  $OSDisk.PurchasePlan.Publisher -Product  $OSDisk.PurchasePlan.Product  # -PromotionCode $OSDisk.PurchasePlan.PromotionCode
                    
                    if ($TargetZoneIsSameAsSourceZone) {
                        $newdiskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Attach -SourceResourceId $originalVM.StorageProfile.OsDisk.ManagedDisk.Id -zone $AzureZone -Tag $OSDiskTags -PurchasePlan $diskPurchasePlan
                    }else {
                        $newdiskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id -zone $AzureZone -Tag $OSDiskTags -PurchasePlan $diskPurchasePlan
                    }

                    }else{
                        Write-WithTime_Using_WriteHost "OS disk has NO purchase plan." -AppendEmptyLine      
                              
                        if ($TargetZoneIsSameAsSourceZone) {
                            #$newdiskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Attach -SourceResourceId $originalVM.StorageProfile.OsDisk.ManagedDisk.Id -zone $AzureZone -Tag $OSDiskTags -PurchasePlan $diskPurchasePlan
                            $newdiskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Attach -SourceResourceId $originalVM.StorageProfile.OsDisk.ManagedDisk.Id -zone $AzureZone -Tag $OSDiskTags
                        }else {
                            $newdiskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id -zone $AzureZone -Tag $OSDiskTags
                        }

                        
                }
               
               if($KeepSameDiskNames -or $TargetZoneIsSameAsSourceZone){
                # use the same disk name
                $newdiskName = $osdiskname 
               }else {
                $newdiskName = $osdiskname + "-z" + $AzureZone
               }
               
               if (-not $TargetZoneIsSameAsSourceZone) {
                Write-WithTime_Using_WriteHost  "Creating OS zonal disk '$newdiskName' from snapshot '$($snapshot.Name)' ..."
                $newdisk = New-AzDisk -Disk $newdiskConfig -ResourceGroupName $VMResourceGroupName -DiskName $newdiskName
               }
               
               # Configure new Zonal OS Disk
               if ($osType -eq "Linux")
               {
                    #Write-Host
                    Write-WithTime_Using_WriteHost "Configuring Linux OS disk '$newdiskName' for Virtual Machine '$VirtualMachineName'... "  
                    if ($TargetZoneIsSameAsSourceZone) {
                        Set-AzVMOSDisk -VM $newVM -CreateOption Attach  -ManagedDiskId $originalVM.StorageProfile.OsDisk.ManagedDisk.Id -Name $osdiskname  -Linux -Caching $originalVM.StorageProfile.OsDisk.Caching > $null
                    }else{
                        Set-AzVMOSDisk -VM $newVM -CreateOption Attach  -ManagedDiskId $newdisk.Id -Name $newdisk.Name  -Linux -Caching $originalVM.StorageProfile.OsDisk.Caching > $null 
                    }
               }
               if ($osType -eq "Windows")
               {
                    #Write-Host
                    Write-WithTime_Using_WriteHost "Configuring Windows OS disk '$newdiskName' Virtual Machine '$VirtualMachineName' ... " 
                    if ($TargetZoneIsSameAsSourceZone) {
                        Set-AzVMOSDisk -VM $newVM -CreateOption Attach  -ManagedDiskId $originalVM.StorageProfile.OsDisk.ManagedDisk.Id -Name $osdiskname  -Windows -Caching $originalVM.StorageProfile.OsDisk.Caching > $null
                    }else {
                        Set-AzVMOSDisk -VM $newVM -CreateOption Attach  -ManagedDiskId $newdisk.Id -Name $newdisk.Name  -Windows -Caching $originalVM.StorageProfile.OsDisk.Caching > $null
                    }    
               }
    
               if ($TargetZoneIsSameAsSourceZone) {
                    # SAME ZONE

                    # Add exisiting Data Disks
                    foreach ($disk in $originalVM.StorageProfile.DataDisks) { 
                        # Write-Host
                        Write-WithTime_Using_WriteHost "Adding data disk '$($disk.Name)' to Virtual Machine '$VirtualMachineName' ..."
                        #Add-AzVMDataDisk -VM $newVM -Name $disk.Name -ManagedDiskId $disk.ManagedDisk.Id -Caching $disk.Caching -Lun $disk.Lun -DiskSizeInGB $disk.DiskSizeGB -CreateOption Attach > $null
                        Add-AzVMDataDisk -VM $newVM  -ManagedDiskId $disk.ManagedDisk.Id -Lun $disk.Lun -Caching $disk.Caching -CreateOption Attach > $null
                    }
               }else{
                    # Snapshot all of the Data disks, and add to the VM
                    foreach ($disk in $originalVM.StorageProfile.DataDisks)
                    {        
                                $OriginalDataDiskName = $disk.Name
                                $DataDisk             = Get-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $OriginalDataDiskName    
                                $DataDiskTags         = $DataDisk.Tags
                                $storageType          = $DataDisk.Sku.Name

                                #snapshot & copy the data disk
                                #$snapshot = New-AzUniqueNameSnapshot -ResourceGroupName $VMResourceGroupName -Location $location -DiskName $disk.Name -SourceDiskResourceId $disk.ManagedDisk.Id
                                $snapshot = New-AzUniqueNameSnapshot -ResourceGroupName $DataDisk.ResourceGroupName -Location $location -Disk $DataDisk

                                if($KeepSameDiskNames){
                                    # use the same disk name
                                    $diskName = $disk.Name

                                    # Create / Copy the exsiting Data disk with a new name
                                    Copy-AzUniqueNameDiskFromSnapshot -ResourceGroupName $VMResourceGroupName -Location $location -Snapshot $snapshot -OriginalDiskName $OriginalDataDiskName -StorageType $storageType -VMDiskZone $VMDiskZone -DiskNamePosfix "orig" -Tags $DataDiskTags

                                    # Delete Original Data disk
                                    Write-WithTime_Using_WriteHost  "Removing original data disk '$OriginalDataDiskName' ..."
                                    Remove-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $OriginalDataDiskName -Force 
                                }else {
                                    $diskName = $disk.Name + "-z" + $AzureZone
                                }                        
                                                                                
                                #$diskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id -zone $AzureZone -Tag $DataDiskTags
                                if ($DataDisk.Sku.Name  -eq "PremiumV2_LRS") {
                                    Write-WithTime_Using_WriteHost  "Configuring new PremiumV2_LRS disk ..."
                                    $diskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id -zone $AzureZone -Tag $DataDiskTags -DiskIOPSReadWrite $DataDisk.DiskIOPSReadWrite -DiskMBpsReadWrite $DataDisk.DiskMBpsReadWrite -DiskIOPSReadOnly $DataDisk.DiskIOPSReadOnly -DiskMBpsReadOnly $DataDisk.DiskMBpsReadOnly
                                }else{
                                    $diskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id -zone $AzureZone -Tag $DataDiskTags
                                }

                                Write-WithTime_Using_WriteHost  "Creating zonal data disk '$diskName' from snapshot '$($snapshot.Name)' ..."
                                #Write-Host
                                $newdisk = New-AzDisk -Disk $diskConfig -ResourceGroupName $VMResourceGroupName -DiskName $diskName # > $null
                                
                                Write-WithTime_Using_WriteHost "Configuring data disk '$($newdisk.Name)' , LUN '$($disk.Lun)' for Virtual Machine '$VirtualMachineName' ... " 
            
                                if($disk.WriteAcceleratorEnabled) {
                                    #Write-Host
                                    Write-WithTime_Using_WriteHost "Adding disk '$($newdisk.Name)' to new VM with enabled Write Accelerator ... "
                                    Add-AzVMDataDisk -VM $newVM -Name $newdisk.Name -ManagedDiskId $newdisk.Id -Caching $disk.Caching -Lun $disk.Lun -DiskSizeInGB $newdisk.DiskSizeGB -CreateOption Attach -WriteAccelerator  > $null    
                                }else{
                                    #Write-Host
                                    Write-WithTime_Using_WriteHost "Adding disk '$($newdisk.Name)' to new VM ... "
                                    Add-AzVMDataDisk -VM $newVM -Name $newdisk.Name -ManagedDiskId $newdisk.Id -Caching $disk.Caching -Lun $disk.Lun -DiskSizeInGB $newdisk.DiskSizeGB -CreateOption Attach > $null    
                                }
                    }
               }
               
             # Add NIC(s) and keep the same NIC as primary
             foreach ($nic in $originalVM.NetworkProfile.NetworkInterfaces) {                  
                #Write-Host
                Write-WithTime_Using_WriteHost "Configuring '$($nic.Id)' network card to Virtual Machine '$VirtualMachineName' ..."
                if ($nic.Primary -eq "True"){                
                    #Write-Host
                    Write-WithTime_Using_WriteHost "NIC is primary."
                    Add-AzVMNetworkInterface -VM $newVM -Id $nic.Id -Primary > $null
                }
                else{                
                    #Write-Host
                    Write-WithTime_Using_WriteHost "NIC is secondary."
                    Add-AzVMNetworkInterface -VM $newVM -Id $nic.Id > $null
                }
            }
    
            if(-not $DoNotCopyTags){
                # Copy the Tags
                #Write-Host
                Write-WithTime_Using_WriteHost "Listing VM '$VirtualMachineName' tags: "
                #Write-Host
                $originalVM.Tags
        
                #Write-Host
                Write-WithTime_Using_WriteHost "Copy Tags ..."
                $newVM.Tags = $originalVM.Tags
                #Write-Host
                Write-WithTime_Using_WriteHost "Tags copy to new VM definition done. "
            }else{            
                #Write-Host
                Write-WithTime_Using_WriteHost "Skipping copy of VM tags:"            
                #Write-Host
                $originalVM.Tags
            }
        
            # Configure Boot Diagnostic account
            if ($originalVM.DiagnosticsProfile.BootDiagnostics.Enabled) {
                #Write-Host
                Write-WithTime_Using_WriteHost "Boot diagnostic account is enabled."
                
                # Get Strage URI
                $StorageUri = $originalVM.DiagnosticsProfile.BootDiagnostics.StorageUri            
    
                if ($StorageUri -eq $null) {
    
                    Write-WithTime_Using_WriteHost "Boot diagnostic URI is empty." #-PrependEmptyLine -AppendEmptyLine
                                
                    Write-WithTime_Using_WriteHost "Configuring boot diganostic with managed storage account ..." # -AppendEmptyLine

                    $newVM = Set-AzVMBootDiagnostic -VM $newVM -Enable
                }else {
                    
                    $BootDiagnosticURI = $originalVM.DiagnosticsProfile.BootDiagnostics.StorageUri.Split("/")[2]
                    #Write-Host
                    Write-WithTime_Using_WriteHost "Boot diagnostic URI: '$BootDiagnosticURI'."
        
                    $staccName = $BootDiagnosticURI.Split(".")[0]
                    #Write-Host
                    Write-WithTime_Using_WriteHost "Extracted storage account name: '$staccName'"
        
                    #Write-Host
                    Write-WithTime_Using_WriteHost "Getting storage account '$staccName'"
                    $stacc = Get-AzStorageAccount | where-object { $_.StorageAccountName.Contains($staccName) }
                    
                    if($stacc  -eq $null ){
                        Write-WithTime_Using_WriteHost "Storage account '$staccName' used for diagonstic account on source VM do not exist." #-PrependEmptyLine -AppendEmptyLine
                                
                        Write-WithTime_Using_WriteHost "Configuring boot diganostic with managed storage account ..."  #-AppendEmptyLine

                        $newVM = Set-AzVMBootDiagnostic -VM $newVM -Enable  
                    }else{
    
                        #Write-Host
                        Write-WithTime_Using_WriteHost "Configuring storage account '$staccName' for VM boot diagnostigs in Azure resource group '$($stacc.ResourceGroupName)' on the new VM ..."
                    
                        $newVM = Set-AzVMBootDiagnostic -VM $newVM -Enable -ResourceGroupName $stacc.ResourceGroupName -StorageAccountName $staccName
    
                        #Write-Host
                        Write-WithTime_Using_WriteHost "Configuring storage account '$staccName' for VM boot diagnostigs done."    
                    }
                    
                }
            }
    
            if ($VMSecurityType -eq "TrustedLaunch") {
                Write-WithTime_Using_WriteHost "Configuring VM security setting to 'TrustedLaunch' ..."  #-PrependEmptyLine
                $NewVM = Set-AzVmSecurityProfile -VM $NewVM -SecurityType "TrustedLaunch"

                Write-WithTime_Using_WriteHost "Configuring VM secure boot '$VMSecureBootEnabled' and vTPM to '$VMVTpmEnabled' ..."  #-PrependEmptyLine
                $NewVM = Set-AzVMUefi -VM $newVM -EnableSecureBoot $VMSecureBootEnabled -EnableVtpm $VMVTpmEnabled
            }

            # Remove the original VM
            $ToDelete = $true

            if(-not $Force){
                Write-Host
                $ToDelete = Get-AzVMDeleteAnswer -VirtualMachineName $VirtualMachineName -ResourceGroupName $VMResourceGroupName
            }
            
            if($ToDelete){
                #Write-Host
                Write-WithTime_Using_WriteHost  "Removing Virtual Machine '$VirtualMachineName' ..."
                #Write-Host
                Remove-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -force            
            }else {
                # Exit
                Return   
            }              
    
            # Create the new VM
            #Write-Host
            Write-WithTime_Using_WriteHost "Recreating Virtual Machine '$VirtualMachineName' as zonal VM in Azure zone '$AzureZone' ..."

            if ($VMLicenseType) {
                New-AzVM -ResourceGroupName $VMResourceGroupName -Location $originalVM.Location -VM $newVM -DisableBginfoExtension -zone $AzureZone -LicenseType $VMLicenseType
            }else {
                New-AzVM -ResourceGroupName $VMResourceGroupName -Location $originalVM.Location -VM $newVM -DisableBginfoExtension -zone $AzureZone
            }

            #New-AzVM -ResourceGroupName $VMResourceGroupName -Location $originalVM.Location -VM $newVM -DisableBginfoExtension -zone $AzureZone
    
            Write-WithTime_Using_WriteHost "Done!"
    
            }
            catch{
               Write-Error  $_.Exception.Message           
           }
    
        }
    
        END {}
    }
    
function New-AzUniqueNameSnapshot {
    <#
    .SYNOPSIS
        Cmdlet will create a disk snapshot with unique name “<DiskName>-snap” .
        If the snapshot with this name exists in the resource group, it will add a number to snapshot name “<DiskName>-snap<Nr>”, starting with 0, and continuing with 1, 2 etc.
     
    .DESCRIPTION
        Cmdlet will create a disk snapshot with unique name “<DiskName>-snap” .
        If the snapshot with this name exists in the resource group, it will add a number to snapshot name “<DiskName>-snap<Nr>”, starting with 0, and continuing with 1, 2 etc.
     
    .EXAMPLE
     
    .LINK
         
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
        [CmdletBinding()]
        param(
                        
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $ResourceGroupName,
            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $Location,
            
            # [Parameter(Mandatory=$True)]
            # [ValidateNotNullOrEmpty()]
            # [string] $DiskName,

            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [object] $Disk,

            # [Parameter(Mandatory=$True)]
            # [ValidateNotNullOrEmpty()]
            # [string] $SourceDiskResourceId,

            [Parameter(Mandatory=$False)]                    
            [string] $SnapshotPosfix = "snap"

        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                        
                $DiskName             = $Disk.Name
                $snapshotName         = $DiskName + "-$($SnapshotPosfix)"   
                $SourceDiskResourceId = $Disk.Id
                $DiskSKU              = $Disk.Sku.Name
               
                # Check if the snapshot with the same name exists
                # Generate autmaticaly new snapshot name by adding 0, 1 , 2 etc.
                $i = 0
                $SnapshotNameIsNotUnique = $True
                do {
                    
                    #Write-Host
                    Write-WithTime_Using_WriteHost  "Checking existance of snapshot '$snapshotName' in resource group '$ResourceGroupName' ..."
                    $CheckSnapshot = Get-AzSnapshot -ResourceGroupName $ResourceGroupName -SnapshotName $snapshotName  -ErrorVariable -notPresent  -ErrorAction SilentlyContinue

                    if ($null -ne $CheckSnapshot ) {
                        #Write-Host
                        Write-WithTime_Using_WriteHost  "Snapshot '$snapshotName' in resource group '$ResourceGroupName' alredy exist."

                        # Create a new snapshot name
                        $snapshotName = $DiskName + "-$($SnapshotPosfix)" + "$i"
                        $i = ++$i
                    }else {
                        #Write-Host
                        Write-WithTime_Using_WriteHost  "Snapshot '$snapshotName' in resource group '$ResourceGroupName' do not exist."

                        #Write-Host
                        Write-WithTime_Using_WriteHost  "Found uniqueu snapshot name '$snapshotName'."
                        $SnapshotNameIsNotUnique = $False
                    }
                } while ($SnapshotNameIsNotUnique)

                if ($DiskSKU -eq "PremiumV2_LRS") {
                    # Premium V2
                    Write-WithTime_Using_WriteHost  "Creating incremental disk snapshot '$snapshotName' of the disk '$DiskName' with disk SKU '$DiskSKU' ..."
                    Write-WithTime_Using_WriteHost  "Incremental snapshots of Premium SSD v2 or Ultra Disks can't be used to create new disks until the background process copying the data into the snapshot has completed. This can take some time!" -Level "WARNING"
                    
                    $snapshotCfg =  New-AzSnapshotConfig -Location $Location -CreateOption copy -SourceResourceId $SourceDiskResourceId -Incremental
                    $snapshot    = New-AzSnapshot -Snapshot $snapshotCfg -SnapshotName $snapshotName -ResourceGroupName $ResourceGroupName  -ErrorAction Stop
                    
                    # Wait until the background process copying the data into the snapshot has completed
                    Get-AzIncrementalSnapshotCopyCompletionPercent -Snapshot $snapshot
                }else {
                    # Premium V1
                    Write-WithTime_Using_WriteHost  "Creating full disk snapshot '$snapshotName' of the disk '$DiskName' with disk SKU '$DiskSKU' ..."
                    
                    $snapshotCfg =  New-AzSnapshotConfig -Location $Location -CreateOption copy -SourceResourceId $SourceDiskResourceId 
                    $snapshot    = New-AzSnapshot -Snapshot $snapshotCfg -SnapshotName $snapshotName -ResourceGroupName $ResourceGroupName  -ErrorAction Stop   
               
                }
                
                # Return snapshot object
               Write-Output $snapshot
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}

##

function Get-AzIncrementalSnapshotCopyCompletionPercent {
    <#
    .SYNOPSIS
        Cmdlet will copy original disk to a new disk from existing disk snapshot, with unique disk name “<OriginalDiskName>-orig”.
        If this with this name already exist, it will add a number to disk name “<DiskName >-orig<Nr>”, starting with 0, and continuing with 1, 2 etc.
     
    .DESCRIPTION
        Cmdlet will copy original disk to a new disk from existing disk snapshot, with unique disk name “<OriginalDiskName>-orig”.
        If this with this name already exist, it will add a number to disk name “<DiskName >-orig<Nr>”, starting with 0, and continuing with 1, 2 etc.
     
    .EXAMPLE
        #Example
     
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
        [CmdletBinding()]
        param(
                        
            # [Parameter(Mandatory=$True)]
            # [ValidateNotNullOrEmpty()]
            # [string] $ResourceGroupName,
            
            # [Parameter(Mandatory=$True)]
            # [ValidateNotNullOrEmpty()]
            # [string] $Location,

            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [Object] $Snapshot
            
            # [Parameter(Mandatory=$True)]
            # [ValidateNotNullOrEmpty()]
            # [string] $OriginalDiskName,

            # [Parameter(Mandatory=$True)]
            # [ValidateNotNullOrEmpty()]
            # [string] $StorageType,

            # [Parameter(Mandatory=$False)]
            # [string] $VMDiskZone = "",

            # [Parameter(Mandatory=$False)]
            # [string] $DiskNamePosfix = "orig",

            # [Parameter(Mandatory=$False)]
            # $Tags

        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                                       
                # get start time
                $StartTime = Get-Date

                while ($Snapshot.CompletionPercent -lt 100) {
                    Write-WithTime_Using_WriteHost  "Waiting until the background process copying the data into the snapshot '$($Snapshot.Name)' has completed. Current completion percentage is $($Snapshot.CompletionPercent) % ...."
                    
                    Start-Sleep 30

                    $Snapshot = Get-AzSnapshot -ResourceGroupName $Snapshot.ResourceGroupName -SnapshotName $Snapshot.Name
                }

                Write-WithTime_Using_WriteHost  "Background process copying the data into the snapshot '$($Snapshot.Name)' has completed. Current completion percentage is $($Snapshot.CompletionPercent) %."
                
                # get end time
                $EndTime = Get-Date
                $ElapsedTime = $EndTime - $StartTime

                Write-WithTime_Using_WriteHost "Total time for background process copying the data into the snapshot '$($Snapshot.Name)': $($ElapsedTime.Days) days, $($ElapsedTime.Hours) hours, $($ElapsedTime.Minutes) minutes, $($ElapsedTime.Seconds) seconds, $($ElapsedTime.Seconds) milliseconds."

            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}

##

function Copy-AzUniqueNameDiskFromSnapshot {
    <#
    .SYNOPSIS
        Cmdlet will copy original disk to a new disk from existing disk snapshot, with unique disk name “<OriginalDiskName>-orig”.
        If this with this name already exist, it will add a number to disk name “<DiskName >-orig<Nr>”, starting with 0, and continuing with 1, 2 etc.
     
    .DESCRIPTION
        Cmdlet will copy original disk to a new disk from existing disk snapshot, with unique disk name “<OriginalDiskName>-orig”.
        If this with this name already exist, it will add a number to disk name “<DiskName >-orig<Nr>”, starting with 0, and continuing with 1, 2 etc.
     
    .EXAMPLE
        #Example
     
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
        [CmdletBinding()]
        param(
                        
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $ResourceGroupName,
            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $Location,

            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            $Snapshot,
            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $OriginalDiskName,

            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $StorageType,

            [Parameter(Mandatory=$False)]                
            [string] $VMDiskZone = "",

            [Parameter(Mandatory=$False)]                    
            [string] $DiskNamePosfix = "orig",

            [Parameter(Mandatory=$False)]                    
            $Tags

        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                                               
                if ($VMDiskZone -eq "") {
                    [bool] $VMIsZonal = $False
                }else {
                 [bool] $VMIsZonal = $True
                }

                # Check if the disk with the same name exists
                # Generate autmaticaly new disk name by adding 0, 1 , 2 etc.
                $i = 0
                $DiskNameIsNotUnique = $True

                $NewDiskName = $OriginalDiskName + "-$DiskNamePosfix"

                do {
                    
                    #Write-Host
                    Write-WithTime_Using_WriteHost  "Checking existance of the disk '$NewDiskName' in resource group '$ResourceGroupName' ..."
                    $CheckDisk = Get-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $NewDiskName  -ErrorVariable -notPresent  -ErrorAction SilentlyContinue

                    if ($null -ne $CheckDisk ) {
                        #Write-Host
                        Write-WithTime_Using_WriteHost  "Disk '$NewDiskName' in resource group '$ResourceGroupName' alredy exist."

                        # Create a new snapshot name
                        $NewDiskName = $OriginalDiskName + "-$($DiskNamePosfix)" + "$i"
                        $i = ++$i
                    }else {
                        #Write-Host
                        Write-WithTime_Using_WriteHost  "Disk '$NewDiskName' in resource group '$ResourceGroupName' do not exist."

                        #Write-Host
                        Write-WithTime_Using_WriteHost  "Found uniqueu disk name '$NewDiskName'."
                        $DiskNameIsNotUnique = $False
                    }
                } while ($DiskNameIsNotUnique)

                 # Create / Copy the exsiting Disks with new name
                 if ($VMIsZonal) {
                     #Write-Host
                     Write-WithTime_Using_WriteHost  "Original disk '$OriginalDiskName' is zonal in zone '$VMDiskZone'."
                     $CopyOSDiskConfig = New-AzDiskConfig -AccountType $StorageType -Location $Location -CreateOption Copy -SourceResourceId $Snapshot.Id -zone $VMDiskZone -Tag $Tags  
                 }else {
                     #Write-Host
                     Write-WithTime_Using_WriteHost  "Original disk '$OriginalDiskName' is not zonal."
                     $CopyOSDiskConfig = New-AzDiskConfig -AccountType $StorageType -Location $Location -CreateOption Copy -SourceResourceId $Snapshot.Id -Tag $Tags
                 }
                                                
                 #Write-Host
                 Write-WithTime_Using_WriteHost  "Copy original disk '$OriginalDiskName' to new disk '$NewDiskName' from snapshot '$($Snapshot.Name)' ..."
                 $Disk = New-AzDisk -Disk $CopyOSDiskConfig -ResourceGroupName $ResourceGroupName -DiskName $NewDiskName -ErrorAction Stop | Out-Null

                 #Write-Host
                 Write-WithTime_Using_WriteHost  "Copied disk '$NewDiskName' can be used to restore original VM."

                 Write-Output $Disk
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}


function Get-AzComputeSKUZonesAvailability {
    <#
    .SYNOPSIS
        This cmdlet returns list of all zones that are available for the VM SKU in certain Azure region. In case that VM SKU is not available in any zone, an exception with error is thrown.
     
    .DESCRIPTION
        This cmdlet returns list of all zones that are available for the VM SKU in certain Azure region. In case that VM SKU is not available in any zone, an exception with error is thrown.
     
    .EXAMPLE
        # get list of all all zones supporting VM SKU "Standard_D64s_v3"
        Get-AzComputeSKUZonesAvailability -Location "westeurope" -VMSKU "Standard_D64s_v3"
     
     
    .EXAMPLE
         
        Example
     
    .LINK
         
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
        [CmdletBinding()]
        param(
                                   
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $Location,

            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            $VMSKU            
        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                                               
                $SKU = Get-AzComputeResourceSku -Location $Location | where  { ($_.Name -eq $VMSKU) }

                if ($null -eq $SKU) {
                    Write-Host
                    Throw "VM SKU '$VMSKU' not found in Azure region '$Location'. Please check your VM SKU size."
                }

                $Zones = $SKU.LocationInfo.Zones

                Write-Output $Zones
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}


##############################
##############################



function Get-AzComputeSKURegionZonesRestrictions {
    <#
    .SYNOPSIS
        Get-AzComputeSKURegionZonesRestrictions
     
    .DESCRIPTION
        Get-AzComputeSKURegionZonesRestrictions
     
    .EXAMPLE
        # get list of all all zones supporting VM SKU "Standard_D64s_v3"
        Get-AzComputeSKURegionZonesRestrictions -Location "westeurope" -VMSKU "Standard_D64s_v3"
     
     
    .EXAMPLE
         
        Example
     
    .LINK
         
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
        [CmdletBinding()]
        param(
                                   
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $Location,

            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            $VMSKU            
        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                             
                #https://learn.microsoft.com/en-us/azure/azure-resource-manager/troubleshooting/error-sku-not-available?tabs=azure-powershell
                
                $SubId = (Get-AzContext).Subscription.Id

                $SKU = Get-AzComputeResourceSku -Location $Location | where  { ($_.Name -eq $VMSKU) }

                if ($null -eq $SKU) {
                    Write-Host
                    Throw "VM SKU '$VMSKU' not found in Azure region '$Location'. Please check your VM SKU size."
                }                

                $RegionRestriction = if (($SKU.Restrictions.Type | Out-String).Contains("Location"))
                                        {
                                            "NotAvailable"
                                        }
                                    else{
                                            "Available"
                                    }
               
                if (($SKU.Restrictions.Type | Out-String).Contains("Zone"))
                                    {
                                        $ZoneRestriction =  "NotAvailable"                                        
                                        $Zones =  ($SKU.Restrictions | where Type -eq "Zone").RestrictionInfo.Zones                                        
                                    }
                                    else{
                                        $ZoneRestriction =  "Available"
                                    }
                
                $VMSKURestrictionsObj = [PSCustomObject]@{
                    "Name" = $VMSKU
                    "Location" = $Location
                    "SubscriptionID" = $SubId
                    RegionalRestriction  = [PSCustomObject]@{
                        "Availability" = $RegionRestriction                        
                    }
                    ZonalRestriction  = [PSCustomObject]@{
                        "Availability" = $ZoneRestriction
                        "Zones"        = $Zones                        
                    }
                }

                Write-Output $VMSKURestrictionsObj
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}


######

function Get-AzComputeSKUGenerationSupport {
    <#
    .SYNOPSIS
        Get-AzComputeSKUGenerationSupport get a VM SKU in a region generation support inforomation - V1 and/or V2.
     
    .DESCRIPTION
        Get-AzComputeSKUGenerationSupport get a VM SKU in a region generation support inforomation - V1 and/or V2.
        Result is returned as an array of strings.
     
    .EXAMPLE
        # get VM SKU "Standard_D64s_v3" support for Generation support - both Gen1 and Gen2 are supported
        $VMSKUGenSupport = Get-AzComputeSKUGenerationSupport -Location eastus -VMSKU Standard_D64s_v3
 
        # this will print:
        # V1
        # V2
        $VMSKUGenSupport
 
    .EXAMPLE
        # get VM SKU "Standard_D11_v2" support for Generation support - only Gen1 ais supported
        $VMSKUGenSupport = Get-AzComputeSKUGenerationSupport -Location eastus -VMSKU Standard_D11_v2
 
        # this will print:
        # V1
        $VMSKUGenSupport
     
    .EXAMPLE
         
        Example
     
    .LINK
         
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
        [CmdletBinding()]
        param(
                                   
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $Location,

            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            $VMSKU            
        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                             
                #https://learn.microsoft.com/en-us/azure/azure-resource-manager/troubleshooting/error-sku-not-available?tabs=azure-powershell
                
                #$SubId = (Get-AzContext).Subscription.Id

                $SKU = Get-AzComputeResourceSku -Location $Location | where  { ($_.Name -eq $VMSKU) }

                if ($null -eq $SKU) {
                    Write-Host
                    Throw "VM SKU '$VMSKU' not found in Azure region '$Location'. Please check your VM SKU size."
                }                

                $VMSKUGenSupport = ($SKU.Capabilities| where Name -eq HyperVGenerations).Value.Split(",")

                Write-Output $VMSKUGenSupport                

                # Write-Output $VMSKURestrictionsObj
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}



#####

function Test-AzComputeSKUAvailability {
    <#
    .SYNOPSIS
        Test-AzComputeSKUAvailability checks if a VM SKU is available in certain Azure region or zone in an Azure region, and it returns $True or $False.
     
    .DESCRIPTION
        Test-AzComputeSKUAvailability checks if a VM SKU is available in certain Azure region or zone in an Azure region, and it returns $True or $False.
         
    .EXAMPLE
        # ZONAL check
        # Checking if VM SKU Standard_D2as_v4 is available in zone 1 in 'East US' region
        Test-AzComputeSKUAvailability -Location "eastus" -VMSKU Standard_D2as_v4 -AzureZone 1
 
    .EXAMPLE
        # REGIONAL check
        # Checking if VM SKU Standard_D2as_v4 is available in a 'East US' region
        Test-AzComputeSKUAvailability -Location "eastus" -VMSKU Standard_D2as_v4
     
    .LINK
         
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
        [CmdletBinding()]
        param(
                                   
            [Parameter(Mandatory = $True, ParameterSetName = "Zonal")]
            [Parameter(Mandatory = $True, ParameterSetName = "Regional")]
            #[Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $Location,

            [Parameter(Mandatory = $True, ParameterSetName = "Zonal")]
            [Parameter(Mandatory = $True, ParameterSetName = "Regional")]            
            #[Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            $VMSKU,
            
            [Parameter(Mandatory = $True, ParameterSetName = "Zonal")]        
            #[Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            $AzureZone  
        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                            
                $VMSKURestrictions = Get-AzComputeSKURegionZonesRestrictions -Location $Location -VMSKU $VMSKU

                # ZONAL check
                if ($AzureZone) {
                    Write-Verbose "Checking VM SKU '$VMSKU' availability in Azure region '$Location' and zone '$AzureZone' ..."
                    if ($VMSKURestrictions.ZonalRestriction.Availability -eq "Available") {
                        Write-Output $true
                    }elseif ($VMSKURestrictions.ZonalRestriction.Zones -contains $AzureZone) {
                        Write-Output $false
                    }else {
                        Write-Output $true
                    } 
                    
                # Regional check
                }else {
                    Write-Verbose "Checking VM SKU '$VMSKU' availability in Azure region '$Location' ..."
                    if ($VMSKURestrictions.RegionalRestriction.Availability -eq "NotAvailable") {
                        Write-Output $false
                    }else {
                        Write-Output $true
                    }
                }               
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}




##############################
##############################




function Test-AzComputeSKUZonesAvailability {
    <#
    .SYNOPSIS
        This cmdlet checks a VM SKU is available in certain Azure zone in an Azure region, and it returns $True or $False.
     
    .DESCRIPTION
      This cmdlet checks a VM SKU is available in certain Azure zone in an Azure region, and it returns $True or $False.
         
    .EXAMPLE
        # Checkign if VM SKU Standard_D64s_v3 is availble in zone 1 in West Europe region
        Test-AzComputeSKUZonesAvailabilit -Location "westeurope" -VMSKU "Standard_D64s_v3" -AzureZone 1
     
    .LINK
         
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
        [CmdletBinding()]
        param(
                                   
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $Location,

            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            $VMSKU,
            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            $AzureZone  
        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                                               
                $VMAvailabilityZones = Get-AzComputeSKUZonesAvailability -Location $Location -VMSKU $VMSKU

                $VMSKUIsAvailableInZone = $False

                foreach ($VMAvailabilityZone in $VMAvailabilityZones) {                    
                    if ($VMAvailabilityZone -eq $AzureZone) {
                        #Write-Host
                        Write-WithTime_Using_WriteHost "VM SKU '$VMSKU' is available in the Azure zone '$AzureZone' in region '$Location'."

                        $VMSKUIsAvailableInZone = $True

                        break
                    }
                }

                # return $true or $false
                Write-Output $VMSKUIsAvailableInZone
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}


###########
function Test-AzRegionZonesAvailability {
    <#
    .SYNOPSIS
        This cmdlet checks if certain Azure region has zones, and it returns $True or $False.
     
    .DESCRIPTION
      This cmdlet checks if certain Azure region has zones, and it returns $True or $False.
         
    .EXAMPLE
        # Check if 'West Europe' region has zones - it returns $True
        Test-AzRegionZonesAvailability -Location "westeurope"
     
    .EXAMPLE
    # Check if 'Switzerland West' region has zones - it returns $False
    Test-AzRegionZonesAvailability -Location "switzerlandwest"
     
    .LINK
         
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
        [CmdletBinding()]
        param(
                                   
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $Location
             
        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                                               
                $RegionVMSKUs = Get-AzComputeResourceSku -Location $Location | where ResourceType -eq virtualMachines

                $NumberofZonesForALLVMSKUs = $RegionVMSKUs.LocationInfo.zones.Count

                if ($NumberofZonesForALLVMSKUs -eq 0) {
                    # region has NO zones
                    return $false
                }else {
                     # region has zones
                    return $True
                }

            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}



    function Test-AzVMIsZonalVM {
    <#
    .SYNOPSIS
        Check if VM is Zonal VM or not.
     
    .DESCRIPTION
        Commanlet check if VM is Zonal VM or not, e.g. it retruns boolian $True or $False
         
     
    .EXAMPLE
        Test-AzVMIsZonalVM -ResourceGroupName gor-Zone-Migration -VirtualMachineName mig-c2
     
     
    .EXAMPLE
         
        $IsVMZonal = Test-AzVMIsZonalVM -ResourceGroupName gor-Zone-Migration -VirtualMachineName mig-c2
        if($IsVMZonal){
            Write-Host "Virtutal Machine is zonal VM."
        }else{
            Write-Host "Virtutal Machine is not zonal VM."
        }
     
    .LINK
         
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
        [CmdletBinding()]
        param(
            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string]$ResourceGroupName,
                  
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $VirtualMachineName
        )
    
        BEGIN{        
              
        }
        
        PROCESS{
            try{   
               
               # get VM and check existance
               $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -ErrorAction Stop   
               
               $Zone = $VM.Zones
               
               if($Zone -eq $Null){
                    return $False           
               }else{
                    return $True
               }                                     
            }
            catch{
               Write-Error  $_.Exception.Message
               Throw $_.Exception.Message
           }
    
        }
    
        END {}
    }
    

    
    
function Get-AzVMStopAnswer {
<#
        .SYNOPSIS
           Get-AzVMStopAnswer gets an asnwer to stop or not the VM.
         
        .DESCRIPTION
            Get-AzVMStopAnswer gets an asnwer to stop or not the VM.
             
         
        .EXAMPLE
            Get-AzVMStopAnswer -ResourceGroupName gor-Zone-Migration -VirtualMachineName mig-c2
         
             
        .LINK
             
         
        .NOTES
            v0.1 - Initial version
         
#>

        
#Requires -Modules Az.Compute
#Requires -Version 5.1
        
    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
                
        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VirtualMachineName
    )

    BEGIN{        
            
    }
    
    PROCESS{
        try{   
            
            write-host ""
            write-host  "Virtual machine '$VirtualMachineName' in the resource group '$ResourceGroupName' will be stopped. Do you want to continue?" 
            $Answer = Read-Host "[Y] Yes [N] No (default is "Y") "

            switch ($Answer) {
                'Y'   { $return = $True  }
                'y'   { $return = $True  }
                'Yes' { $return = $True  }
                'yes' { $return = $True  }
                'N'   { $return = $False }
                'n'   { $return = $False }
                'No'  { $return = $False }
                'no'  { $return = $False }
                Default {$return = $True}                       
            }
            
            return $return           
                                                
        }
        catch{
            Write-Error  $_.Exception.Message           
        }

    }

    END {}
}
        
function Get-AzVMDeleteAnswer {
    <#
            .SYNOPSIS
            Get-AzVMDeleteAnswer gets an asnwer to confirm deletion of the VM.
             
            .DESCRIPTION
            Get-AzVMDeleteAnswer gets an asnwer to confirm deletion of the VM.
             
            .EXAMPLE
            Get-AzVMDeleteAnswer -ResourceGroupName gor-Zone-Migration -VirtualMachineName mig-c2
                             
            .LINK
             
            .NOTES
                v0.1 - Initial version
             
    #>

            
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
            
        [CmdletBinding()]
        param(
            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $ResourceGroupName,
                    
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $VirtualMachineName
        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                
                write-host ""
                write-host  "Virtual machine '$VirtualMachineName' in the resource group '$ResourceGroupName' will be deleted. Do you want to continue?" 
                $Answer = Read-Host "[Y] Yes [N] No (default is "Y") "
    
                switch ($Answer) {
                    'Y'   { $return = $True  }
                    'Yes' { $return = $True  }
                    'yes' { $return = $True  }
                    'N'   { $return = $False }
                    'No'  { $return = $False }
                    'no'  { $return = $False }
                    Default {$return = $True}                       
                }
                
                return $return           
                                                    
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
    }
            

function Get-Answer {
    <#
            .SYNOPSIS
            Get-Answer gets an asnwer.
             
            .DESCRIPTION
            Get-Answer gets an asnwer.
             
            .EXAMPLE
            Get-Answer "Do you really want to do XYZ?"
                             
            .LINK
             
            .NOTES
                v0.1 - Initial version
             
    #>

            
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
            
        [CmdletBinding()]
        param(
            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $TextQuestion
                    
            
        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                
                write-host ""
                write-host  $TextQuestion
                $Answer = Read-Host "[Y] Yes [N] No (default is "Y") "
                write-host ""

    
                switch ($Answer) {
                    'Y'   { $return = $True  }
                    'Yes' { $return = $True  }
                    'yes' { $return = $True  }
                    'N'   { $return = $False }
                    'No'  { $return = $False }
                    'no'  { $return = $False }
                    Default {$return = $True}                       
                }
                
                return $return           
                                                    
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}
            
    
function Get-AzSAPApplicationServerStopAnswer {
<#
        .SYNOPSIS
        Get-AzSAPApplicationServerStopAnswer gets an asnwer to confirm SAP application server stop.
         
        .DESCRIPTION
        Get-AzSAPApplicationServerStopAnswer gets an asnwer to confirm SAP application server stop.
         
        .EXAMPLE
        Get-AzSAPApplicationServerStopAnswer -SAPSID "TS1" -SAPInstanceNumber 1 -VMResourceGroupName "ts1-resource-group" -VirtualMachineName "ts1-di-0"
                         
        .LINK
         
        .NOTES
            v0.1 - Initial version
         
#>

        
#Requires -Modules Az.Compute
#Requires -Version 5.1
        
    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
        [ValidateLength(3, 3)]
        [string] $SAPSID,
    
        [Parameter(Mandatory = $True)]
        [ValidateRange(0, 99)]
        [ValidateLength(1, 2)]
        [string] $SAPInstanceNumber,

        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMResourceGroupName,
                        
        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VirtualMachineName


    )

    BEGIN{        
            
    }
    
    PROCESS{
        try{   
            
            write-host ""
            write-host  "SAP application server with instance number '$SAPInstanceNumber' and SAP SID '$SAPSID' on virtual machine '$VirtualMachineName' in the resource group '$VMResourceGroupName' will be stopped. Do you want to continue?" 
            $Answer = Read-Host "[Y] Yes [N] No (default is "Y") "

            switch ($Answer) {
                'Y'   { $return = $True  }
                'Yes' { $return = $True  }
                'yes' { $return = $True  }
                'N'   { $return = $False }
                'No'  { $return = $False }
                'no'  { $return = $False }
                Default {$return = $True}                       
            }
            
            return $return           
                                                
        }
        catch{
            Write-Error  $_.Exception.Message           
        }

    }

    END {}
}
function Move-AzSAPApplicationServerVMToAzureAvaialbilitySetAndOrProximityPlacementGroup {
    <#
     
    .SYNOPSIS
    Cmdlet moves an SAP application server (AS) VM into an Availability Set and/or Proximity Placement Group. SAP AS is gracefully stopped and after VM migration SAP AS is started.
     
    .DESCRIPTION
     
    Cmdlet moves an SAP application server (AS) VM into an:
     
    - Availability Set
    - Proximity Placement Group
    - Availability Set and Proximity Placement Group
     
    SAP application server (AS) is gracefully stopped and after VM migration SAP AS is started.
     
    You can use the Cmdlet to:
     
    - Move SAP Application Server VM to new Availability Set
     
    - Move SAP Application Server VM to new Availability Set and Proximity Placement Group:
     
        - It can be used in Azure Zones context, where you move SAP Application Server to Zone.
            One group of SAP Application Servers are indirectly part of Zone1 (via AvSet1 and PPGZone1), and other part of SAP Application Servers are indirectly part of Zone2 (via AvSet2 and PPGZone2).
            First anchor VM (this is DBMS VM) is already deployed in a Zone and same Proximity Placement Group
     
        - It can be used to move an SAP Application Server from current AvSet1 and PPGZone1 to AvSet2 and PPGZone2, e.g. indirectly from Zone1 to Zone2.
            First anchor VM (this is DBMS VM) is already deployed in a Zone2 and same Proximity Placement Group 2 (PPGZone2).
     
        - It can be used in non-Zonal context, where group of SAP Application Servers are part of new Av Set and Proximity Placement Group, together with the SAP ASCS and DB VM that are part of one SAP SID.
     
    - Group SAP Application Server VM to Proximity Placement Group
     
    The Cmdlet deletes the VM definition and recreates it preserving networking and storage configuration.
     
    There is no need to reinstall the operating system.
     
    VM can be with Windows or Linux OS.
     
    IMPORTANT: The Cmdlet does not preserve VM extensions.
                Also, the Cmdlet does not support VMs with public IP addresses.
                Zonal VMs are not supported, because you cannot combine Availability Set and Zone.
                Availability Set and VM must be members of the same Azure resource group.
                Proximity Placement Group can be member of some other resource group.
     
    MORE INFO:
        - SAP workload configurations with Azure Availability Zones : https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/sap-ha-availability-zones
        - Regions and Availability Zones in Azure: https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/sap-ha-availability-zones
     
             
    .PARAMETER SAPSID
    SAP SID
     
    .PARAMETER SAPInstanceNumber
    SAP Instance Number to connect
     
    .PARAMETER SIDADMUserCredential
    SAP <sid>adm user name and password. Need ONLY on WIndows.
     
    .PARAMETER PathToSAPControl
    Full path to SAP Control executable. Need ONLY on WIndows.
     
    .PARAMETER SoftShutdownTimeInSeconds
    Soft / gracefull shutdown time for SAP application server to stop. Deafult is 300 sec.
     
    .PARAMETER SAPApplicationServerStartWaitTimeInSeconds
    Time to wait for SAP application server to start aftre VM is migrated. Deafult is 300 sec.
     
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
     
    .PARAMETER VMResourceGroupName
    Resource Group Name of the VM and Availability Set.
         
    .PARAMETER VirtualMachineName
    Virtual Machine Name name.
     
    .PARAMETER AvailabilitySetName
    Availability Set Name
     
    .PARAMETER PPGResourceGroupName
    Resource group name of the Proximity Placement Group
     
    .PARAMETER ProximityPlacementGroupName
    Proximity Placement Group Name
     
    .PARAMETER DoNotCopyTags
    Switch paramater. If specified, VM tags will NOT be copied.
     
    .PARAMETER NewVMSize
    If NewVMSize is specified , VM will be set to a new VM size. Otherwise, original VM size will be used.
     
    .PARAMETER Force
    Forces the command to run without asking for user confirmation.
    If not set, user will be asked for confirmation to:
        - Stop SAP application server
        - Stop VM
        - Recreate VM
     
    .EXAMPLE
        # Move SAP applictaion sever with LINUX VM
        # you will need to confirm stopping of SAP application server, stopping of VM, and VM deletion.
         
        $SAPSID = "TS1"
        $SAPInstanceNumber = 3
        $SAPApplicationServerGracefullSoftShutdownTimeInSeconds = 600
        $VMResourceGroupName = "gor-linux-eastus2"
        $VirtualMachineName = "ts2-di2"
        $AvailabilitySetName= "TS1-AV-SET-ZONE2"
        $PPGResourceGroupName = "gor-linux-eastus2-2"
        $ProximityPlacementGroupName = "TS1-PPG-Zone2"
 
        Move-AzSAPApplicationServerVMToAzureAvaialbilitySetAndOrProximityPlacementGroup -SAPSID $SAPSID -SAPInstanceNumber $SAPInstanceNumber -SoftShutdownTimeInSeconds $SAPApplicationServerGracefullSoftShutdownTimeInSeconds -VMResourceGroupName $VMResourceGroupName -VirtualMachineName $VirtualMachineName -AvailabilitySetName $AvailabilitySetName -PPGResourceGroupName $PPGResourceGroupName -ProximityPlacementGroupName $ProximityPlacementGroupName
 
    .EXAMPLE
        # Move SAP applictaion sever with LINUX VM
        # As '-Force' swith parameter is used, user will not be asked for any confirmation
         
        $SAPSID = "TS1"
        $SAPInstanceNumber = 3
        $SAPApplicationServerGracefullSoftShutdownTimeInSeconds = 600
        $VMResourceGroupName = "gor-linux-eastus2"
        $VirtualMachineName = "ts2-di2"
        $AvailabilitySetName= "TS1-AV-SET-ZONE2"
        $PPGResourceGroupName = "gor-linux-eastus2-2"
        $ProximityPlacementGroupName = "TS1-PPG-Zone2"
 
        Move-AzSAPApplicationServerVMToAzureAvaialbilitySetAndOrProximityPlacementGroup -SAPSID $SAPSID -SAPInstanceNumber $SAPInstanceNumber -SoftShutdownTimeInSeconds $SAPApplicationServerGracefullSoftShutdownTimeInSeconds -VMResourceGroupName $VMResourceGroupName -VirtualMachineName $VirtualMachineName -AvailabilitySetName $AvailabilitySetName -PPGResourceGroupName $PPGResourceGroupName -ProximityPlacementGroupName $ProximityPlacementGroupName -Force
 
     
    .EXAMPLE
        # Move SAP applictaion sever with Windows VM
        # As '-Force' swith parameter is used, user will not be asked for any confirmation
     
        $SAPSID = "PR2"
        $SIDADM = $SAPSID.ToLower() + "adm"
        $SAPSIDADMUserCred = Get-Credential -UserName $SIDADM -Message 'Enter Password:'
 
 
        $SAPInstanceNumber = 2
        $SAPApplicationServerGracefullSoftShutdownTimeInSeconds = 600
        $FullPathToSAPControl = "C:\usr\sap\PR2\D02\exe\sapcontrol.exe"
 
        $VMResourceGroupName = "gor-linux-eastus2-2"
        $VirtualMachineName = "pr2-di-1"
        $AvailabilitySetName= "PR2-AvSet-Zone3"
        $PPGResourceGroupName = "gor-linux-eastus2"
        $ProximityPlacementGroupName = "PR2-PPG_Zone3"
 
        Move-AzSAPApplicationServerWindowsVMToAzureAvaialbilitySetAndOrProximityPlacementGroup -SAPSID $SAPSID -SAPInstanceNumber $SAPInstanceNumber -SIDADMUserCredential $SAPSIDADMUserCred -PathToSAPControl $FullPathToSAPControl -SoftShutdownTimeInSeconds $SAPApplicationServerGracefullSoftShutdownTimeInSeconds -VMResourceGroupName $VMResourceGroupName -VirtualMachineName $VirtualMachineName -AvailabilitySetName $AvailabilitySetName -PPGResourceGroupName $PPGResourceGroupName -ProximityPlacementGroupName $ProximityPlacementGroupName -Force
 
     
    .LINK
        https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/sap-ha-availability-zones
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
            [CmdletBinding()]
            param(            
                
                [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
                [ValidateLength(3, 3)]
                [string] $SAPSID,
            
                [Parameter(Mandatory = $True)]
                [ValidateRange(0, 99)]
                [ValidateLength(1, 2)]
                [string] $SAPInstanceNumber,

                [Parameter(Mandatory=$False)]
                [ValidateNotNull()]
                [System.Management.Automation.PSCredential]
                [System.Management.Automation.Credential()]
                $SIDADMUserCredential,                  
        
                [Parameter(Mandatory = $False)]
                [ValidateNotNullOrEmpty()] 
                [string] $PathToSAPControl,
                
                [Parameter(Mandatory = $False)] 
                [int] $SoftShutdownTimeInSeconds = "300",
    
                [Parameter(Mandatory = $False)] 
                [int] $SAPApplicationServerStartWaitTimeInSeconds = "300",
        
                [Parameter(Mandatory = $False)] 
                [bool] $PrintExecutionCommand = $False,
                
                [Parameter(Mandatory=$True)]
                [ValidateNotNullOrEmpty()]        
                [string] $VMResourceGroupName,
                                
                [Parameter(Mandatory=$True)]
                [ValidateNotNullOrEmpty()]        
                [string] $VirtualMachineName,
                                                
                [Parameter(Mandatory=$False)]
                [string] $AvailabilitySetName,
        
                [Parameter(Mandatory=$False)]
                [string] $PPGResourceGroupName,
                    
                [Parameter(Mandatory=$False)]
                [string] $ProximityPlacementGroupName,
                    
                [switch] $DoNotCopyTags,
        
                [Parameter(Mandatory=$False)]
                [string] $NewVMSize,
        
                [switch] $Force
            )
        
            BEGIN{        
                $ProximityPlacementGroupExist = $False    
            }
            
            PROCESS{
                try{                  
        
                    # Check if resource group exists. If $False exit
                    Confirm-AzResoureceGroupExist -ResourceGroupName $VMResourceGroupName             
        
                    # Check if VM. If $False exit
                    Write-Host
                    Confirm-AzVMExist -ResourceGroupName $VMResourceGroupName -VMName $VirtualMachineName            
        
                    $OSType = Get-AzVMOSType -VMName $VirtualMachineName -ResourceGroupName $VMResourceGroupName                
    
                    if ($OSType -eq "Windows") {                              
                        Move-AzSAPApplicationServerWindowsVMToAzureAvaialbilitySetAndOrProximityPlacementGroup -SAPSID $SAPSID -SAPInstanceNumber $SAPInstanceNumber  -SIDADMUserCredential $SIDADMUserCredential -PathToSAPControl $PathToSAPControl -SoftShutdownTimeInSeconds $SoftShutdownTimeInSeconds -SAPApplicationServerStartWaitTimeInSeconds $SAPApplicationServerStartWaitTimeInSeconds -PrintExecutionCommand $PrintExecutionCommand -VMResourceGroupName $VMResourceGroupName -VirtualMachineName $VirtualMachineName -AvailabilitySetName $AvailabilitySetName -PPGResourceGroupName $PPGResourceGroupName -ProximityPlacementGroupName $ProximityPlacementGroupName -NewVMSize $NewVMSize -DoNotCopyTags:$DoNotCopyTags -Force:$Force
                    }else{
                        # Linux
                        Move-AzSAPApplicationServerLinuxVMToAzureAvaialbilitySetAndOrProximityPlacementGroup   -SAPSID $SAPSID -SAPInstanceNumber $SAPInstanceNumber -SoftShutdownTimeInSeconds $SoftShutdownTimeInSeconds -SAPApplicationServerStartWaitTimeInSeconds $SAPApplicationServerStartWaitTimeInSeconds -PrintExecutionCommand $PrintExecutionCommand -VMResourceGroupName $VMResourceGroupName -VirtualMachineName $VirtualMachineName -AvailabilitySetName $AvailabilitySetName -PPGResourceGroupName $PPGResourceGroupName -ProximityPlacementGroupName $ProximityPlacementGroupName -NewVMSize $NewVMSize -DoNotCopyTags:$DoNotCopyTags -Force:$Force   
                    }
                }
                catch{
                    Write-Error  $_.Exception.Message           
                }
        
            }
        
            END {}
        }            
    
    ###
function Move-AzSAPApplicationServerLinuxVMToAzureAvaialbilitySetAndOrProximityPlacementGroup {
    <#
    .SYNOPSIS
        Cmdlet moves an SAP application server (AS) on Linux VM into an Availability Set and/or Proximity Placement Group. SAP AS is gracefully stopped and after VM migration SAP AS is started.
     
    .DESCRIPTION
        Cmdlet moves an SAP application server (AS) on Linux VM into an Availability Set and/or Proximity Placement Group. SAP AS is gracefully stopped and after VM migration SAP AS is started. This functionality should be called from cmdlet Move-AzSAPApplicationServerVMToAzureAvaialbilitySetAndOrProximityPlacementGroup.
             
    .PARAMETER SAPSID
    SAP SID
 
    .PARAMETER SAPInstanceNumber
    SAP Instance Number to connect
     
    .PARAMETER SoftShutdownTimeInSeconds
    Soft / gracefull shutdown time for SAP application server to stop. Deafult is 300 sec.
 
    .PARAMETER SAPApplicationServerStartWaitTimeInSeconds
    Time to wait for SAP application server to start aftre VM is migrated. Deafult is 300 sec.
 
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
 
    .PARAMETER VMResourceGroupName
    Resource Group Name of the VM and Availability Set.
         
    .PARAMETER VirtualMachineName
    Virtual Machine Name name.
 
    .PARAMETER AvailabilitySetName
    Availability Set Name
 
    .PARAMETER PPGResourceGroupName
    Resource group name of the Proximity Placement Group
 
    .PARAMETER ProximityPlacementGroupName
    Proximity Placement Group Name
 
    .PARAMETER DoNotCopyTags
    Switch paramater. If specified, VM tags will NOT be copied.
 
    .PARAMETER NewVMSize
    If NewVMSize is specified , VM will be set to a new VM size. Otherwise, original VM size will be used.
 
    .PARAMETER Force
    Forces the command to run without asking for user confirmation.
    If not set, user will be asked for confirmation to:
    - Stop SAP application server
    - Stop VM
    - Recreate VM
     
    .LINK
         
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
        [CmdletBinding()]
        param(            
            
            [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
            [ValidateLength(3, 3)]
            [string] $SAPSID,
        
            [Parameter(Mandatory = $True)]
            [ValidateRange(0, 99)]
            [ValidateLength(1, 2)]
            [string] $SAPInstanceNumber,
            
            [Parameter(Mandatory = $False)] 
            [int] $SoftShutdownTimeInSeconds = "300",

            [Parameter(Mandatory = $False)] 
            [int] $SAPApplicationServerStartWaitTimeInSeconds = "300",
    
            [Parameter(Mandatory = $False)] 
            [bool] $PrintExecutionCommand = $False,
            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $VMResourceGroupName,
                            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $VirtualMachineName,
                                            
            [Parameter(Mandatory=$False)]
            [string] $AvailabilitySetName,
    
            [Parameter(Mandatory=$False)]              
            [string] $PPGResourceGroupName,
                
            [Parameter(Mandatory=$False)]
            [string] $ProximityPlacementGroupName,
                
            [switch] $DoNotCopyTags,
    
            [Parameter(Mandatory=$False)]
            [string] $NewVMSize,
    
            [switch] $Force            
        )
    
        BEGIN{        
            $ProximityPlacementGroupExist = $False    
        }
        
        PROCESS{
            try{                                  
            
                Write-Host
                Write-WithTime_Using_WriteHost "Virtual machine '$VirtualMachineName' is Linux machine."
                                
                $VMIsRunning = Test-AzVMIsStarted -ResourceGroupName  $VMResourceGroupName -VMName $VirtualMachineName                       

                # Stop SAP Application server
                if ($VMIsRunning -eq $True) {
                    # Get SAP System Status
                    Write-Host
                    Write-WithTime_Using_WriteHost "Getting SAP system instances and status ...."
                    Write-Host            
                    Get-AzSAPSystemStatusLinux  -ResourceGroupName $VMResourceGroupName -VMName $VirtualMachineName -InstanceNumberToConnect $SAPInstanceNumber -SAPSID $SAPSID -PrintExecutionCommand $PrintExecutionCommand -PrintOutputWithWriteHost
                    
                    # Stop SAP application server
                    $ToStopSAPApplicationServer = $true
                    if(-not $Force){
                        Write-Host
                        $ToStopSAPApplicationServer = Get-AzSAPApplicationServerStopAnswer -SAPSID $SAPSID -SAPInstanceNumber $SAPInstanceNumber -VMResourceGroupName $VMResourceGroupName -VirtualMachineName $VirtualMachineName 
                    }
                
                    if($ToStopSAPApplicationServer){                    
                        Write-Host                    
                        Stop-AzSAPApplicationServerLinux  -ResourceGroupName $VMResourceGroupName -VMName $VirtualMachineName -SAPInstanceNumber $SAPInstanceNumber -SAPSID $SAPSID -SoftShutdownTimeInSeconds $SoftShutdownTimeInSeconds -PrintExecutionCommand $PrintExecutionCommand -PrintOutputWithWriteHost                                                

                    }else {                        
                        Return  
                    }     

                    # Get SAP System Status
                    Write-Host
                    Write-WithTime_Using_WriteHost "Getting SAP system instances and status after SAP application server stop ...."
                    Write-Host            
                    Get-AzSAPSystemStatusLinux  -ResourceGroupName $VMResourceGroupName -VMName $VirtualMachineName -InstanceNumberToConnect $SAPInstanceNumber -SAPSID $SAPSID -PrintExecutionCommand $PrintExecutionCommand -PrintOutputWithWriteHost                         
                }
                
                # Move VM to Av Set / PPG
                $ret = Move-AzVMToAvailabilitySetAndOrProximityPlacementGroup -VMResourceGroupName $VMResourceGroupName -VirtualMachineName $VirtualMachineName -AvailabilitySetName $AvailabilitySetName -PPGResourceGroupName $PPGResourceGroupName -ProximityPlacementGroupName $ProximityPlacementGroupName -DoNotCopyTags:$DoNotCopyTags -NewVMSize $NewVMSize -Force:$Force -ErrorAction Stop                
                
                # Exit
                if($null -eq $ret ){                                                        
                    return
                }               

                # Start VM
                Write-Host
                Write-WithTime_Using_WriteHost "Starting VM '$VirtualMachineName' in Azure Resource Group '$VMResourceGroupName' ..."
                Start-AzVM  -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -WarningAction "SilentlyContinue"

                $VM = Get-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -Status
                $VMStatus = $VM.Statuses[1].DisplayStatus
                #Write-Host ""
                Write-Host "Virtual Machine '$VirtualMachineName' status: $VMStatus"
           
                # Wait for 180 sec
                Write-Host
                Write-WithTime_Using_WriteHost  "Waiting for 180 seconds to start operating system SAP services ...."
                Start-Sleep 180   
                                    
                Write-Host
                Start-AzSAPApplicationServerLinux -ResourceGroupName $VMResourceGroupName -VMName $VirtualMachineName -SAPInstanceNumber $SAPInstanceNumber -SAPSID $SAPSID -WaitTime $SAPApplicationServerStartWaitTimeInSeconds  -PrintExecutionCommand $PrintExecutionCommand 

                # Get SAP System Status
                Write-Host
                Write-WithTime_Using_WriteHost "Getting SAP system instances and status ...."
                Write-Host            
                Get-AzSAPSystemStatusLinux  -ResourceGroupName $VMResourceGroupName -VMName $VirtualMachineName -InstanceNumberToConnect $SAPInstanceNumber -SAPSID $SAPSID -PrintExecutionCommand $PrintExecutionCommand     
                
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}            

function Move-AzSAPApplicationServerWindowsVMToAzureAvaialbilitySetAndOrProximityPlacementGroup {
    <#
    .SYNOPSIS
        Cmdlet moves an SAP application server (AS) on Windows VM into an Availability Set and/or Proximity Placement Group. SAP AS is gracefully stopped and after VM migration SAP AS is started.
     
    .DESCRIPTION
        Cmdlet moves an SAP application server (AS) on Windows VM into an Availability Set and/or Proximity Placement Group. SAP AS is gracefully stopped and after VM migration SAP AS is started. This functionality should be called from cmdlet Move-AzSAPApplicationServerVMToAzureAvaialbilitySetAndOrProximityPlacementGroup.
     
    .PARAMETER SAPSID
    SAP SID
 
    .PARAMETER SAPInstanceNumber
    SAP Instance Number to connect
 
    .PARAMETER SAPsidadmUserPassword
    SAP <sid>adm user password. Need ONLY on WIndows.
 
    .PARAMETER PathToSAPControl
    Full path to SAP Control executable. Need ONLY on WIndows.
 
    .PARAMETER SoftShutdownTimeInSeconds
    Soft / gracefull shutdown time for SAP application server to stop. Deafult is 300 sec.
 
    .PARAMETER SAPApplicationServerStartWaitTimeInSeconds
    Time to wait for SAP application server to start aftre VM is migrated. Deafult is 300 sec.
 
    .PARAMETER PrintExecutionCommand
    If set to $True, it will print execution command.
 
    .PARAMETER VMResourceGroupName
    Resource Group Name of the VM and Availability Set.
         
    .PARAMETER VirtualMachineName
    Virtual Machine Name name.
 
    .PARAMETER AvailabilitySetName
    Availability Set Name
 
    .PARAMETER PPGResourceGroupName
    Resource group name of the Proximity Placement Group
 
    .PARAMETER ProximityPlacementGroupName
    Proximity Placement Group Name
 
    .PARAMETER DoNotCopyTags
    Switch paramater. If specified, VM tags will NOT be copied.
 
    .PARAMETER NewVMSize
    If NewVMSize is specified , VM will be set to a new VM size. Otherwise, original VM size will be used.
 
    .PARAMETER Force
    Forces the command to run without asking for user confirmation.
    If not set, user will be asked for confirmation to:
    - Stop SAP application server
    - Stop VM
    - Recreate VM
     
    .LINK
         
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
        [CmdletBinding()]
        param(            
            
            [Parameter(Mandatory = $True, HelpMessage = "SAP System <SID>. 3 characters , starts with letter.")] 
            [ValidateLength(3, 3)]
            [string] $SAPSID,
        
            [Parameter(Mandatory = $True)]
            [ValidateRange(0, 99)]
            [ValidateLength(1, 2)]
            [string] $SAPInstanceNumber,

            [Parameter(Mandatory=$True)]
            [ValidateNotNull()]
            [System.Management.Automation.PSCredential]
            [System.Management.Automation.Credential()]
            $SIDADMUserCredential,                    
    
            [Parameter(Mandatory = $False)]
            [ValidateNotNullOrEmpty()] 
            [string] $PathToSAPControl,
            
            [Parameter(Mandatory = $False)] 
            [int] $SoftShutdownTimeInSeconds = "300",

            [Parameter(Mandatory = $False)] 
            [int] $SAPApplicationServerStartWaitTimeInSeconds = "300",
    
            [Parameter(Mandatory = $False)] 
            [bool] $PrintExecutionCommand = $False,
            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $VMResourceGroupName,
                            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $VirtualMachineName,
                                            
            [Parameter(Mandatory=$False)]
            [string] $AvailabilitySetName,
    
            [Parameter(Mandatory=$False)]            
            [string] $PPGResourceGroupName,
                
            [Parameter(Mandatory=$False)]
            [string] $ProximityPlacementGroupName,
                
            [switch] $DoNotCopyTags,
    
            [Parameter(Mandatory=$False)]
            [string] $NewVMSize,
    
            [switch] $Force
        )
    
        BEGIN{        
            $ProximityPlacementGroupExist = $False    
        }
        
        PROCESS{
            try{                                         
                    
                #Write-Verbose "Passed parameters: SAPSID = '$SAPSID' ; SAPInstanceNumber = '$SAPInstanceNumber' ; SAPsidadmUserPassword = '$SAPsidadmUserPassword' ; PathToSAPControl = '$PathToSAPControl' ; SoftShutdownTimeInSeconds = '$SoftShutdownTimeInSeconds' ; SAPApplicationServerStartWaitTimeInSeconds = '$SAPApplicationServerStartWaitTimeInSeconds' ; PrintExecutionCommand = '$PrintExecutionCommand' ; VMResourceGroupName = '$VMResourceGroupName' ; VirtualMachineName = '$VirtualMachineName' ; AvailabilitySetName = '$AvailabilitySetName' ; PPGResourceGroupName = '$PPGResourceGroupName' ; ProximityPlacementGroupName = '$ProximityPlacementGroupName' ; DoNotCopyTags = '$DoNotCopyTags' ; NewVMSize = '$NewVMSize' ; Force = '$Force'"

                # Windows
                Write-Host
                Write-WithTime_Using_WriteHost "Virtual machine '$VirtualMachineName' is Windows machine."

                $VMIsRunning = Test-AzVMIsStarted -ResourceGroupName  $VMResourceGroupName -VMName $VirtualMachineName                       

                # Stop SAP Application server
                if ($VMIsRunning -eq $True) {

                    #$SIDADMUser = $SAPSID.ToLower() + "adm"

                    #$SIDADMUserName = $SIDADMUserCredential.UserName
                    $SAPsidadmUserPassword = $SAPSIDADMUserCred.GetNetworkCredential().Password
                    
                    #if($SAPsidadmUserPassword -eq ""){
                    # Write-Host
                    # Write-Error "On Windows you need to specify '$SIDADMUser' password using '-SAPsidadmUserPassword' parameter." -ErrorAction Stop
                    }if ( $PathToSAPControl -EQ "") {
                        Write-Host
                        Write-Error  "On Windows you need to full local path to sapcontrol.exe executable using '-PathToSAPControl' parameter." -ErrorAction Stop
                                        
                    }else{
                        # Get SAP System Status
                        Write-Host
                        Write-WithTime_Using_WriteHost "Getting SAP system instances and status ...."
                        Write-Host                                

                        Get-AzSAPSystemStatusWindows -ResourceGroupName $VMResourceGroupName -VMName $VirtualMachineName -InstanceNumberToConnect $SAPInstanceNumber -SAPSID $SAPSID -PathToSAPControl $PathToSAPControl -SAPSidPwd  $SAPsidadmUserPassword  -PrintExecutionCommand $PrintExecutionCommand -PrintOutputWithWriteHost                    

                        # Stop SAP application server
                        $ToStopSAPApplicationServer = $true
                        if(-not $Force){
                            Write-Host
                            $ToStopSAPApplicationServer = Get-AzSAPApplicationServerStopAnswer -SAPSID $SAPSID -SAPInstanceNumber $SAPInstanceNumber -VMResourceGroupName $VMResourceGroupName -VirtualMachineName $VirtualMachineName 
                        }

                        if($ToStopSAPApplicationServer){                        
                            Write-Host
                            Stop-AzSAPApplicationServerWindows -ResourceGroupName $VMResourceGroupName -VMName $VirtualMachineName -SAPSID $SAPSID -SAPInstanceNumber $SAPInstanceNumber  -PathToSAPControl $PathToSAPControl -SAPSidPwd  $SAPsidadmUserPassword -SoftShutdownTimeInSeconds $SoftShutdownTimeInSeconds -PrintExecutionCommand $PrintExecutionCommand -PrintOutputWithWriteHost   
                        }else {                            
                            Return   
                        }    

                            # Get SAP System Status
                            Write-Host
                            Write-WithTime_Using_WriteHost "Getting SAP system instances and status after SAP application server stop ...."
                            Write-Host                                
    
                            Get-AzSAPSystemStatusWindows -ResourceGroupName $VMResourceGroupName -VMName $VirtualMachineName -InstanceNumberToConnect $SAPInstanceNumber -SAPSID $SAPSID -PathToSAPControl $PathToSAPControl -SAPSidPwd  $SAPsidadmUserPassword  -PrintExecutionCommand $PrintExecutionCommand -PrintOutputWithWriteHost                    
    
                    }
                #}

                $ret = Move-AzVMToAvailabilitySetAndOrProximityPlacementGroup -VMResourceGroupName $VMResourceGroupName -VirtualMachineName $VirtualMachineName -AvailabilitySetName $AvailabilitySetName -PPGResourceGroupName $PPGResourceGroupName -ProximityPlacementGroupName $ProximityPlacementGroupName -DoNotCopyTags:$DoNotCopyTags -NewVMSize $NewVMSize -Force:$Force -ErrorAction Stop                

                # Exit
                if($null -eq $ret ){                                                        
                    return
                }

                # Start VM
                Write-Host
                Write-WithTime_Using_WriteHost "Starting VM '$VirtualMachineName' in Azure Resource Group '$VMResourceGroupName' ..."
                Start-AzVM  -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -WarningAction "SilentlyContinue"

                $VM = Get-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -Status
                $VMStatus = $VM.Statuses[1].DisplayStatus
                #Write-Host ""
                Write-WithTime_Using_WriteHost "Virtual Machine '$VirtualMachineName' status: $VMStatus"

                #Wait for 180 sec
                Write-Host
                Write-WithTime_Using_WriteHost  "Waiting for 300 seconds to start operating system SAP services ...."
                Start-Sleep 300   
                                    
                Write-Host
                Start-AzSAPApplicationServerWindows -ResourceGroupName $VMResourceGroupName -VMName $VirtualMachineName -SAPSID $SAPSID -SAPInstanceNumber $SAPInstanceNumber -PathToSAPControl $PathToSAPControl -SAPSidPwd $SAPsidadmUserPassword -WaitTime $SAPApplicationServerStartWaitTimeInSeconds -PrintExecutionCommand $PrintExecutionCommand 
                        
                # Get SAP System Status
                Write-Host
                Write-WithTime_Using_WriteHost "Getting SAP system instances and status ...."
                Write-Host            
                Get-AzSAPSystemStatusWindows -ResourceGroupName $VMResourceGroupName -VMName $VirtualMachineName -InstanceNumberToConnect $SAPInstanceNumber -SAPSID $SAPSID -PathToSAPControl $PathToSAPControl -SAPSidPwd  $SAPsidadmUserPassword  -PrintExecutionCommand $PrintExecutionCommand -PrintOutputWithWriteHost                    
                        
                
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
    }
                

function Get-AzVMBelongingProximityPlacementGroup {
    <#
    .SYNOPSIS
        Cmdlet is getting list of VMs which belongs to certain Proximity Placement Group (PPG).
     
    .DESCRIPTION
        Cmdlet is getting list of VMs which belongs to certain Proximity Placement Group (PPG).
        If PPG do not contain any VM, and no object is returned.
     
    .PARAMETER ResourceGroupName
    Resource Group Name of Proximity Placement Group.
         
    .PARAMETER PPGName
    Proximity Placement Group name.
 
    .EXAMPLE
        Get-AzVMBelongingProximityPlacementGroup -ResourceGroupName gor-zrs-westeurope -PPGName ppg-we-z1
     
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
    #>
    
    
        [CmdletBinding()]
        param(
                        
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $ResourceGroupName,
            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $PPGName

        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                                
                $PPG = Get-AzProximityPlacementGroup -ResourceGroupName $ResourceGroupName -Name $PPGName
                
                $PPGVMsIds =  $PPG.VirtualMachines.Id
                
                foreach ($PPGVMId in $PPGVMsIds) {
                    
                    $obj = New-Object -TypeName psobject

                    $VMResource = Get-AzResource -ResourceId  $PPGVMId
                    $VMName                 = $VMResource.Name
                    $VMResourceGroupName    = $VMResource.ResourceGroupName
                    $VMLocation             = $VMResource.Location
                    $VMId                   = $VMResource.ResourceId

                    $VM = Get-AzVM -ResourceGroupName $VMResourceGroupName -Name $VMName
                    $VMZone = $VM.Zones

                    $obj | add-member  -NotePropertyName "VMName" -NotePropertyValue $VMName 
                    $obj | add-member  -NotePropertyName "VMResourceGroupName" -NotePropertyValue $VMResourceGroupName
                    $obj | add-member  -NotePropertyName "VMLocation" -NotePropertyValue $VMLocation
                    $obj | add-member  -NotePropertyName "VMZone" -NotePropertyValue $VMZone
                    $obj | add-member  -NotePropertyName "VMId" -NotePropertyValue $VMId

                    #Return formated object
                    Write-Output $obj      
                }                                  
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}

function Test-AzVMHasAzureSharedDisks {
    <#
    .SYNOPSIS
                 
     
    .DESCRIPTION
          
     
    .PARAMETER ResourceGroupName
     
         
    .PARAMETER PPGName
     
 
    .EXAMPLE
         
     
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
    #>
    
    
        [CmdletBinding()]
        param(
                        
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $ResourceGroupName,
            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $VMName

        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                   
                $VMHasSharedDataDisks = $false

                $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName

                # Get the data disks atatched to VM
                $DataDisks =  $VM.StorageProfile.DataDisks

                foreach ($DataDisk in $DataDisks) {
                    $DataDiskId = $DataDisk.ManagedDisk.Id
                   
                    # get info on disk name and RG
                    $DiskInfo = Get-AzResource -ResourceId $DataDiskId
                    $DiskName = $DiskInfo.Name
                    $DiskResourceGroupName = $DiskInfo.ResourceGroupName

                    # Get info on MaxShares - is it Azure shared disk
                    $DetailedDiskInfo = Get-AzDisk -ResourceGroupName $DiskResourceGroupName -DiskName $DiskName                    

                    if ($DetailedDiskInfo.MaxShares -gt 1) {
                        Write-Host
                        Write-WithTime_Using_WriteHost  "Azure VM '$VMName' data disk '$DiskName' is Azure shared disk with MaxShared value of '$($DetailedDiskInfo.MaxShares)'"
                        $VMHasSharedDataDisks = $True
                    }
                }

                return $VMHasSharedDataDisks                        
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}

###
function Test-AzVMHasPremiumV2Disks {
    <#
    .SYNOPSIS
                 
     
    .DESCRIPTION
          
     
    .PARAMETER ResourceGroupName
     
         
    .PARAMETER PPGName
     
 
    .EXAMPLE
         
     
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
    #>
    
    
        [CmdletBinding()]
        param(
                        
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $ResourceGroupName,
            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $VMName

        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                   
                $VMHasPremiumV2dDataDisks = $false

                $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName

                # Get the data disks atatched to VM
                $DataDisks =  $VM.StorageProfile.DataDisks

                foreach ($DataDisk in $DataDisks) {
                    $DataDiskId = $DataDisk.ManagedDisk.Id
                   
                    # get info on disk name and RG
                    $DiskInfo = Get-AzResource -ResourceId $DataDiskId
                    $DiskName = $DiskInfo.Name
                    $DiskResourceGroupName = $DiskInfo.ResourceGroupName

                    $DetailedDiskInfo = Get-AzDisk -ResourceGroupName $DiskResourceGroupName -DiskName $DiskName                    

                    if ($DetailedDiskInfo.Sku.Name -eq "PremiumV2_LRS") {
                        Write-Host
                        Write-WithTime_Using_WriteHost  "Azure VM '$VMName' data disk '$DiskName' is Azure 'PremiumV2_LRS' disk.'"
                        $VMHasPremiumV2dDataDisks = $True
                    }
                }

                return $VMHasPremiumV2dDataDisks                        
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}


function Test-AzVMHasUltraDisks {
    <#
    .SYNOPSIS
                 
     
    .DESCRIPTION
          
     
    .PARAMETER ResourceGroupName
     
         
    .PARAMETER PPGName
     
 
    .EXAMPLE
         
     
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
    #>
    
    
        [CmdletBinding()]
        param(
                        
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $ResourceGroupName,
            
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $VMName

        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                   
                $VMHasUltraDataDisks = $false

                $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName

                # Get the data disks atatched to VM
                $DataDisks =  $VM.StorageProfile.DataDisks

                foreach ($DataDisk in $DataDisks) {
                    $DataDiskId = $DataDisk.ManagedDisk.Id
                   
                    # get info on disk name and RG
                    $DiskInfo = Get-AzResource -ResourceId $DataDiskId
                    $DiskName = $DiskInfo.Name
                    $DiskResourceGroupName = $DiskInfo.ResourceGroupName

                    $DetailedDiskInfo = Get-AzDisk -ResourceGroupName $DiskResourceGroupName -DiskName $DiskName                    

                    if ($DetailedDiskInfo.Sku.Name -eq "UltraSSD_LRS") {
                        Write-Host
                        Write-WithTime_Using_WriteHost  "Azure VM '$VMName' data disk '$DiskName' is Azure 'UltraSSD_LRS' disk.'"
                        $VMHasUltraDataDisks = $True
                    }
                }

                return $VMHasUltraDataDisks                        
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}

####
Function Move-AzSAPVMToVMSSFlex {
<#
    .SYNOPSIS
    Move-AzSAPVMToVMSSFlex cmdlet move an SAP VM to an Virtual MachineScale Set with Flexible Orchestration (VMSS Flex).
 
    .DESCRIPTION
    Move-AzSAPVMToVMSSFlex cmdlet move an SAP VM to an Virtual MachineScale Set with Flexible Orchestration (VMSS Flex).
     
    If -AzureZone is specified, VM will be moved also to the zone and zonal VMSS Flex, and if needed VM will be converted to the zonal VM (in case of non-zonal source VM).
 
    If -AzureZone is NOT specified, VM will be moved to the non-zone/regional VMSS Flex.
    For SAP use case, this is supported only in Azure regions without zones.
     
    .PARAMETER VMResourceGroupName
    Virtual Machine Resource Group Name
             
    .PARAMETER VirtualMachineName
    Virtual Machine Name
 
    .PARAMETER VMSSGResourceGroupName
    Virtual MachineScale Set (VMSS) Resource Group Name
 
    .PARAMETER VMSSName
    Virtual MachineScale Set (VMSS) Name
 
    #.PARAMETER FaultDomainNumber
    #Fault Domain Number
 
    .PARAMETER AzureZone
    Azure zone number - 1, 2 or 3.
  
    .PARAMETER DoNotCopyTags
    Switch paramater. If specified, VM tags will NOT be copied.
         
    .PARAMETER Force
    Forces the command to run without asking for user confirmation.
 
    .EXAMPLE
    # move VM to an VMSS and Azure zone - THIS IS SUPPORTED FOR SAP ONLY in Azure REGIONS WITH ZONES!!
     
    $ResourceGroupName = "sap-ab1"
    $VMName = "ab1-ascs"
    $VMSSName = "sap-flex-zones-ab1"
    $Location = "westeurope"
 
    # Create ZONAL VMSS Flex with PlatformFaultDomainCount = 1, and configure for zones 1, 2 and 3
    $vmssConfig = New-AzVmssConfig -Location $Location -PlatformFaultDomainCount 1 -Zone @(1,2,3)
    New-AzVmss -ResourceGroupName $ResourceGroupName -Name $VMSSName -VirtualMachineScaleSet $vmssConfig
 
    # Move VM to VMSS Flex and Zone 2
    Move-AzSAPVMToVMSSFlex -VMResourceGroupName $ResourceGroupName -VirtualMachineName $VMName -VMSSGResourceGroupName $ResourceGroupName -VMSSName $VMSSName -AzureZone 2
 
    .EXAMPLE
    # move VM to an Azure region without zones - THIS IS SUPPORTED FOR SAP ONLY IN Azure REGIONS WITH NO ZONES!!
    $ResourceGroupName = "sap-ab1-swis-west"
    $VMSSName = "sap-vmss-ab1"
    $Location = "switzerlandwest"
    $VMName = "ab1-ascs"
 
    # Create non zonal VMSS Flex with PlatformFaultDomainCount = 1
    $vmssConfig = New-AzVmssConfig -Location $Location -PlatformFaultDomainCount 1
    New-AzVmss -ResourceGroupName $ResourceGroupName -Name $VMSSName -VirtualMachineScaleSet $vmssConfig
 
    # Move VM to non zonal VMSS Flex
    Move-AzSAPVMToVMSSFlex -VMResourceGroupName $ResourceGroupName -VirtualMachineName $VMName -VMSSGResourceGroupName $ResourceGroupName -VMSSName $VMSSName
 
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
#>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
        
            [CmdletBinding()]
            param(
                                
                [Parameter(Mandatory, ParameterSetName="Regional")]
                #[Parameter(Mandatory, ParameterSetName="RegionalAndPPG")]
                [Parameter(Mandatory, ParameterSetName="Zonal")]
                [ValidateNotNullOrEmpty()]        
                [string] $VMResourceGroupName,
                      
                [Parameter(Mandatory, ParameterSetName="Regional")]
                #[Parameter(Mandatory, ParameterSetName="RegionalAndPPG")]
                [Parameter(Mandatory, ParameterSetName="Zonal")]                
                [ValidateNotNullOrEmpty()]        
                [string] $VirtualMachineName,
                                                       
                [Parameter(Mandatory, ParameterSetName="Regional")]
                #[Parameter(Mandatory, ParameterSetName="RegionalAndPPG")]
                [Parameter(Mandatory, ParameterSetName="Zonal")]                     
                [string] $VMSSGResourceGroupName,   

                [Parameter(Mandatory, ParameterSetName="Regional")]
                [Parameter(Mandatory, ParameterSetName="RegionalAndPPG")]
                [Parameter(Mandatory, ParameterSetName="Zonal")]     
                [string] $VMSSName,   
                                                
                #[Parameter(Mandatory, ParameterSetName="RegionalAndPPG")]
                #[string] $PPGResourceGroupName,
                        
                #[Parameter(Mandatory, ParameterSetName="RegionalAndPPG")]
                #[string] $ProximityPlacementGroupName,
                
                #[Parameter(Mandatory, ParameterSetName="Regional")]
                #[Parameter(Mandatory, ParameterSetName="RegionalAndPPG")]
                #[ValidateNotNullOrEmpty()]
                #[int] $FaultDomainNumber,
                                
                [Parameter(Mandatory, ParameterSetName="Zonal")]    
                [ValidateRange(1,3)]
                [int] $AzureZone,
    
                [Parameter(ParameterSetName="Regional")]
                #[Parameter(ParameterSetName="RegionalAndPPG")]
                [Parameter(ParameterSetName="Zonal")]                     
                [switch] $DoNotCopyTags,       

                [Parameter(ParameterSetName="Regional")]
                #[Parameter(ParameterSetName="RegionalAndPPG")]
                [Parameter(ParameterSetName="Zonal")]                                     
                [switch] $Force                
            )
        
            BEGIN{
            }
            
            PROCESS{
                try{                                             
                    
                    if($AzureZone){                        
                        Write-WithTime_Using_WriteHost "Starting migration of Virtual Machine '$VirtualMachineName' to the Virtual Machine Scale Set '$VMSSName' and Azure zone '$AzureZone' ..."
                    }else{                        
                        Write-WithTime_Using_WriteHost "Starting migration of Virtual Machine '$VirtualMachineName' to the Virtual Machine Scale Set '$VMSSName' ..."
                    }
                    
                    # Check if VM. If $False exit
                    Confirm-AzVMExist -ResourceGroupName $VMResourceGroupName -VMName $VirtualMachineName

                    # get VMSS Flex
                    Write-WithTime_Using_WriteHost  "Getting Virtual Machine Scale Set '$VMSSName' in Azure resource group '$VMSSGResourceGroupName' ..."
                    $VmssFlex = Get-AzVmss -ResourceGroupName $VMSSGResourceGroupName -VMScaleSetName $VMSSName -ErrorAction Stop

                    # Check if VMSS is Flex type
                    $VMSSIsFlexType = Test-AzVMSSIsFlexType -VMSSGResourceGroupName $VMSSGResourceGroupName  -VMSSName $VMSSName 

                    if($VMSSIsFlexType){                    
                        Write-WithTime_Using_WriteHost  "Orchestration mode of the Virtual Machine Scale Set '$VMSSName' in Azure resource group '$VMSSGResourceGroupName' is 'Flexible'. This is supported for SAP deplyments." 
                    }else{
                        Throw "Orchestration mode of the Virtual Machine Scale Set '$VMSSName' in Azure resource group '$VMSSGResourceGroupName' is not 'Flexible'. This is NOT suported for SAP deplyments. For SAP deplyments you need 'Flexible' orchestration mode."
                    }
                
                    # Check if VMSS has PlatformFaultDomainCount = 1
                    if($VmssFlex.PlatformFaultDomainCount -ne 1){
                        #Write-Host
                        Throw  "Virtual Machine Scale Set '$VMSSName' in Azure resource group '$VMSSGResourceGroupName' Platform Fault Domain Count is '$($VmssFlex.PlatformFaultDomainCount)'. Platform fault domain count must be set to '1'."
                    }else {
                        Write-WithTime_Using_WriteHost  "Virtual Machine Scale Set '$VMSSName' in Azure resource group '$VMSSGResourceGroupName' has platform fault domain count set to 1."
                    }

                    Write-WithTime_Using_WriteHost  "Starting Virtual Machine '$VirtualMachineName' ..."
                    $StartVMStatus = Start-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -ErrorAction Stop 
                    
                    # Get the VM and check existance
                    Write-WithTime_Using_WriteHost  "Getting Virtual Machine '$VirtualMachineName' configuration ..."
                    $originalVM = Get-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -ErrorAction Stop     
                    

                    # Check if VMSS Flex region is same as VM region
                    Write-WithTime_Using_WriteHost  "Virtual Machine Scale Set '$VMSSName' in Azure resource group '$VMSSGResourceGroupName' is deployed in Azure region '$($VmssFlex.Location)'."
                    if($VmssFlex.Location -ne $originalVM.Location){
                        Throw "'$VMSSName' Virtual Machine Scale Set '$($VmssFlex.Location)' region is different than '$($originalVM.Name)' virtual machine '$($originalVM.Location)' region. Virtual Machine Scale Set and VM must be configured in the same Azure region."
                    }else {
                        Write-WithTime_Using_WriteHost  "Virtual Machine Scale Set '$VMSSName' in Azure resource group '$VMSSGResourceGroupName' is deployed in the same Azure region '$($VmssFlex.Location)' as the VM '$($originalVM.Name)'."
                    }
                    
                    # Checking if Region has zones
                    Write-WithTime_Using_WriteHost  "Checking if Azure region '$($originalVM.Location)' has zones ..."
                    $AzureRegionHasZones = Test-AzRegionZonesAvailability -Location $originalVM.Location
                    if ($AzureRegionHasZones) {
                        Write-WithTime_Using_WriteHost  "Azure region '$($originalVM.Location)' has zones."
                    }else {
                        Write-WithTime_Using_WriteHost  "Azure region '$($originalVM.Location)' has no zones."
                    }

                    # Check if VM is zonal VM
                    Write-WithTime_Using_WriteHost  "Checking if Virtual Machine '$VirtualMachineName' is zonal VM ..."
                    $VMIsZonal = Test-AzVMIsZonalVM -ResourceGroupName $VMResourceGroupName  -VirtualMachineName $VirtualMachineName
                    
                    # Check if VM is using Standard Load Balancer
                    $VMIsUsingStandardLoadBalancer =  Test-AzVMLoadbBalancerIsStandard -VMResourceGroupName $VMResourceGroupName -VirtualMachineName $VirtualMachineName
                    if (-not $VMIsUsingStandardLoadBalancer ) {
                        Throw "Virtual machine '$VirtualMachineName' in resource group '$VMResourceGroupName' is configured with Azure load balancer which is NOT Standard . This is NOT supported for Azure Virtual Machine Scale Set. Please convert Azure Load Blancer to standard type before you proceed."
                    }
                    
                    Write-WithTime_Using_WriteHost  "VM '$VirtualMachineName' identity type is '$($originalVM.Identity.Type)'."

                    # Premium V2 disks is not suported
                    # Write-WithTime_Using_WriteHost "Checking if virtual machine '$VirtualMachineName' has Azure Premium V2 data disks ... "
                    # $VMHasAzurePremiumV2Disks = Test-AzVMHasPremiumV2Disks -ResourceGroupName $VMResourceGroupName -VMName $VirtualMachineName
                    # if ($VMHasAzurePremiumV2Disks) {
                    # #Write-Host
                    # Throw "VM '$VirtualMachineName' has Azure Azure Premium V2 data disks. Azure Premium V2 data disks are not supported."
                    # }else {
                    # Write-WithTime_Using_WriteHost "Virtual machine '$VirtualMachineName' has no Azure Premium V2 data disks."
                    # }

                     # Ultra disks is not suported
                     Write-WithTime_Using_WriteHost  "Checking if virtual machine '$VirtualMachineName' has Azure Ultra data disks ... "
                     $VMHasAzureUltraDisks = Test-AzVMHasUltraDisks -ResourceGroupName  $VMResourceGroupName -VMName $VirtualMachineName
                     if ($VMHasAzureUltraDisks) {
                         #Write-Host
                         Throw "VM '$VirtualMachineName' has Azure Azure Ultra data disks. Azure Ultra data disks are not supported."
                     }else {                            
                             Write-WithTime_Using_WriteHost  "Virtual machine '$VirtualMachineName' has no Azure Ultra data disks."
                     }

                    # Azure shared disks are not supported
                    Write-WithTime_Using_WriteHost  "Checking if virtual machine '$VirtualMachineName' has Azure shared data disks ... "
                    $VMHasAzureSharedDisks = Test-AzVMHasAzureSharedDisks -ResourceGroupName  $VMResourceGroupName -VMName $VirtualMachineName
                    if ($VMHasAzureSharedDisks) {
                            #Write-Host
                            Throw "VM '$VirtualMachineName' has Azure shared disk. Azure shared disks are not supported."
                    }else {                            
                            Write-WithTime_Using_WriteHost  "Virtual machine '$VirtualMachineName' has no Azure shared data disks."
                    }

                    # Get ALL VMs and roles
                    # $ALLVMsAndRoles = Get-AzVMsAndRoles -VMResourceGroupName $VMResourceGroupName -VirtualMachineName $VirtualMachineName

                    # move to the zonal VM
                    if($AzureZone){                    

                        if (-not $AzureRegionHasZones) {
                            Throw  "Azure region '$($originalVM.Location)' has no zones. You cannot migrate regional VM to a zonal VM in a region that do not have zones. Please remove parameter '-AzureZone <ZoneNumber>' from Move-AzSAPVMToVMSSFlex cmdlet. "
                        }

                        # Get confiramtion to migrate to the zone
                        if (-not $VMIsZonal) {
                            Write-WithTime_Using_WriteHost  "Virtual Machine '$VirtualMachineName' is not zonal VM."

                            $ToContinue = $true               

                            if(-not $Force){
                                #Write-Host
                                $ToContinue = Get-Answer "Virtual Machine '$VirtualMachineName' in resource group '$VMResourceGroupName' is not zonal VM. You chose to move VM to the zone VM. VM will be converted to the zonal VM. Do you agree to continue?"    
                            }
                                            
                            if(-not $ToContinue){             
                                Return   
                            }
                            
                        }
                                            
                        <# # Check if VMSS has PlatformFaultDomainCount = 1
                        if($VmssFlex.PlatformFaultDomainCount -ne 1){
                            Write-Host
                            Throw "Virtual Machine Scale Set '$VMSSName' in Azure resource group '$VMSSGResourceGroupName' Platform Fault Domain Count is '$($VmssFlex.PlatformFaultDomainCount)'. As you migrate VM to the Azure zone, Platform Fault Domain Count must be set to '1'."
                        } #>
                                                                   

                        # We don't support moving machines with public IPs, since those are zone specific.
                        foreach ($nic in $originalVM.NetworkProfile.NetworkInterfaces) {
                            $thenic = $nic.id
                            $nicname = $thenic.substring($thenic.LastIndexOf("/")+1)
                            $othernic = Get-AzNetworkInterface -name $nicname -ResourceGroupName $VMResourceGroupName 
                            #Write-Host
                            Write-WithTime_Using_WriteHost "Found Network Card '$nicname' in Azure resource group '$VMResourceGroupName'."
                
                            foreach ($ipc in $othernic.IpConfigurations) {
                                $pip = $ipc.PublicIpAddress
                                if ($pip) { 
                                    Throw  "Sorry, machines with public IPs are not supported by this script" 
                                    #exit
                                }
                            }
                        }
            
                        [string] $osType             = $originalVM.StorageProfile.OsDisk.OsType
                        [string] $location           = $originalVM.Location
                        [string] $storageType        = $originalVM.StorageProfile.OsDisk.ManagedDisk.StorageAccountType
                        [string] $OSDiskName         = $originalVM.StorageProfile.OsDisk.Name
                        # when non-Zonal disk / VM this value is an empty string
                        [string] $VMDiskZone         = $originalVM.Zones
                        $OSDisk                      = Get-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $OSDiskName    
                        $OSDiskTags                  = $OSDisk.Tags  
                        $VMPlan                      = $originalVM.Plan
                        $VMSecurityType              = $originalVM.SecurityProfile.SecurityType
                        [bool] $VMSecureBootEnabled  = $originalVM.SecurityProfile.UefiSettings.SecureBootEnabled
                        [bool] $VMVTpmEnabled        = $originalVM.SecurityProfile.UefiSettings.VTpmEnabled
                        $VMIdentity                  = $originalVM.Identity
                        $VMLicenseType               = $originalVM.LicenseType

                        if ($VMIsZonal) {
                            if ($AzureZone -eq  $originalVM.Zones[0]) {
                                $TargetZoneIsSameAsSourceZone = $true 
                                Write-WithTime_Using_WriteHost "Target zone '$AzureZone' is the same as the source VM zone. No need to migrate the disks to another zone."   
                            }else {
                                $TargetZoneIsSameAsSourceZone = $False
                                Write-WithTime_Using_WriteHost "Target zone '$AzureZone' is different to the source VM zone '$($originalVM.Zones[0])'. Need to migrate disks to new zone '$AzureZone'."  
                            }
                        }
                        
                        Write-WithTime_Using_WriteHost "Checking Hyper-V Generation of virtual machine '$VirtualMachineName' in resource group '$VMResourceGroupName' ..." 
                        $VMStatus = Get-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -Status
                        $GenType = $VMStatus.HyperVGeneration
                        if ($GenType -eq "V1") {
                            Write-WithTime_Using_WriteHost "Hyper-V Generation of virtual machine '$VirtualMachineName' in resource group '$VMResourceGroupName' is 'V1' - VM is Generation 1."
                        }elseif ($GenType -eq "V2") {
                            Write-WithTime_Using_WriteHost "Hyper-V Generation of virtual machine '$VirtualMachineName' in resource group '$VMResourceGroupName' is 'V2' - VM is Generation 2."
                        }

                        if ($VMSecurityType) {
                            Write-WithTime_Using_WriteHost "VM security type is '$VMSecurityType'."
                            Write-WithTime_Using_WriteHost "VM secure boot is set to '$VMSecureBootEnabled'."
                            Write-WithTime_Using_WriteHost "VM vTPM (Trusted Platform Module) is set to '$VMVTpmEnabled'."
                        }else {
                            Write-WithTime_Using_WriteHost "VM security type is 'Standard'."
                        }
                           
                        if ($VMDiskZone -eq "") {
                            [bool] $VMIsZonal = $False
                        }else {
                            [bool] $VMIsZonal = $True
                        }

                        $OriginalVMSize =  $originalVM.HardwareProfile.VmSize
                        $VMSize = $OriginalVMSize                                        
                                
                        # Check if VM SKU is available in the desired Azure zone
                        $VMSKUIsAvailableinZone = Test-AzVMSKUZonalAvailability -VMSize $VMSize -location $location -AzureZone $AzureZone
                        if(-not $VMSKUIsAvailableinZone){
                            #exit the cmdlet
                            return
                        }                                      
                        
                        # Check if target zone is configured in VMSS
                        $Zones = Get-AzVMSSZones -VMSSName $VMSSName -VMSSGResourceGroupName $VMSSGResourceGroupName

                        if($null -eq $Zones){
                            Throw "Virtual Machine Scale Set '$VMSSName' in Azure resource group '$VMSSGResourceGroupName' has no configured zones! "
                        }else{
                            $foundZone = $false
                            foreach($zone in $Zones){
                                if($zone -eq $AzureZone){
                                    $foundZone = $True
                                    
                                    Write-WithTime_Using_WriteHost "Desired target Azure zone '$AzureZone' is configured in Virtual Machine Scale Set '$VMSSName' in Azure resource group '$VMSSGResourceGroupName'."
                                    break
                                }
                            }
                            if (-not $foundZone) {
                                Throw "Desired target Azure zone '$AzureZone' is NOT configured in Virtual Machine Scale Set '$VMSSName' in Azure resource group '$VMSSGResourceGroupName'! Virtual Machine Scale Set '$VMSSName' is configured with these zones: $Zones"
                            }
                        }

                        # Shutdown the original VM
                        $ToStop = $true               

                        if(-not $Force){
                            $ToStop = Get-AzVMStopAnswer -VirtualMachineName $VirtualMachineName -ResourceGroupName $VMResourceGroupName
                        }
                                    
                        if($ToStop){                        
                                Write-WithTime_Using_WriteHost  "Stopping Virtual Machine '$VirtualMachineName' in resource group '$VMResourceGroupName' ..."
                                Stop-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -force -ErrorAction Stop 
                        }else {
                                # Exit
                                Return   
                        }
                                    
                        # Export original VM configuration
                        #Write-Host
                        Export-VMConfigurationToJSONFile -VM  $originalVM      
                                            
                        Write-WithTime_Using_WriteHost "Configuring Virtual Machine to use Azure Zone '$AzureZone' ..."

                        $newVM = Set-AzVirtualMachineConfiguration -OriginalVM $originalVM -VMSize $VMSize -AzureZone $AzureZone -VmssFlex $VmssFlex 

                        if ($VMSecurityType -eq "TrustedLaunch") {
                            Write-WithTime_Using_WriteHost "Configuring VM security setting to 'TrustedLaunch' ..." 
                            $NewVM = Set-AzVmSecurityProfile -VM $NewVM -SecurityType "TrustedLaunch"
                        
                            Write-WithTime_Using_WriteHost "Configuring VM secure boot '$VMSecureBootEnabled' and vTPM to '$VMVTpmEnabled' ..."
                            $NewVM = Set-AzVMUefi -VM $newVM -EnableSecureBoot $VMSecureBootEnabled -EnableVtpm $VMVTpmEnabled
                        }                        

                        if (-not $TargetZoneIsSameAsSourceZone) {
                            $snapshot = New-AzUniqueNameSnapshot -ResourceGroupName $OSDisk.ResourceGroupName -Location $location -Disk $OSDisk

                            # Create / Copy the exsiting OS Disk with new name
                            Copy-AzUniqueNameDiskFromSnapshot -ResourceGroupName $VMResourceGroupName -Location $location -Snapshot $snapshot -OriginalDiskName $OSDiskName -StorageType $storageType -VMDiskZone $VMDiskZone -DiskNamePosfix "orig" -Tags $OSDiskTags
                        }
                        
                        # Do not delete OS and Data disks during the VM deletion - set DeleteOption to 'Detach'
                        Set-AzVMDisksDeleteOption -VM $originalVM -DeleteOption "Detach" -ErrorAction Stop                              

                        # Do not delete NIC cards during the VM deletion - Set NIC Cards to 'Detach'
                        Set-AzVMNICsDeleteOption -VM $originalVM -DeleteOption "Detach" -ErrorAction Stop             
                        
                        # Remove the original VM -this is a prerequisit to delete orignial OS and data disks
                        $ToDelete = $true

                        if(-not $Force){
                            #Write-Host
                            $ToDelete = Get-AzVMDeleteAnswer -VirtualMachineName $VirtualMachineName -ResourceGroupName $VMResourceGroupName
                        }
                        
                        if($ToDelete){
                            #Write-Host
                            Write-WithTime_Using_WriteHost  "Removing Virtual Machine '$VirtualMachineName' ..." # -AppendEmptyLine
                            Remove-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -force            
                        }else {
                            # Exit
                            Return   
                        }              

                        if (-not $TargetZoneIsSameAsSourceZone) {
                             # Delete Original OS Disk
                            Write-WithTime_Using_WriteHost  "Removing original OS disk '$osdiskname' ..." #-AppendEmptyLine
                            Remove-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $osdiskname -Force                                         

                            $newdiskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id -zone $AzureZone -Tag $OSDiskTags
                            $newdiskName = $osdiskname 

                            #Write-Host
                            Write-WithTime_Using_WriteHost  "Creating OS zonal disk '$newdiskName' from snapshot '$($snapshot.Name)' ..."
                            $newdisk = New-AzDisk -Disk $newdiskConfig -ResourceGroupName $VMResourceGroupName -DiskName $newdiskName                    
                        }
                       
                        # Configure new Zonal OS Disk
                        if ($osType -eq "Linux")
                        {                            
                                Write-WithTime_Using_WriteHost "Configuring Linux OS disk '$osdiskname' for Virtual Machine '$VirtualMachineName'... "
                                
                                if ($TargetZoneIsSameAsSourceZone) {
                                    Set-AzVMOSDisk -VM $newVM -CreateOption Attach  -ManagedDiskId $originalVM.StorageProfile.OsDisk.ManagedDisk.Id -Name  $osdiskname  -Linux -Caching $originalVM.StorageProfile.OsDisk.Caching > $null
                                }else {
                                    Set-AzVMOSDisk -VM $newVM -CreateOption Attach  -ManagedDiskId $newdisk.Id -Name $newdisk.Name  -Linux -Caching $originalVM.StorageProfile.OsDisk.Caching > $null
                                }
                                }
                        if ($osType -eq "Windows")
                        {
                                #Write-Host
                                Write-WithTime_Using_WriteHost "Configuring Windows OS disk '$osdiskname' Virtual Machine '$VirtualMachineName' ... "
                                if ($TargetZoneIsSameAsSourceZone) {
                                    Set-AzVMOSDisk -VM $newVM -CreateOption Attach  -ManagedDiskId $originalVM.StorageProfile.OsDisk.ManagedDisk.Id -Name  $osdiskname  -Windows -Caching $originalVM.StorageProfile.OsDisk.Caching > $null    
                                }else {
                                    Set-AzVMOSDisk -VM $newVM -CreateOption Attach  -ManagedDiskId $newdisk.Id -Name $newdisk.Name  -Windows -Caching $originalVM.StorageProfile.OsDisk.Caching > $null    
                                }
                        }
                
                        if ($TargetZoneIsSameAsSourceZone) {
                            # SAME ZONE

                            # Add exisiting Data Disks
                            foreach ($disk in $originalVM.StorageProfile.DataDisks) { 
                                # Write-Host
                                Write-WithTime_Using_WriteHost "Adding data disk '$($disk.Name)' to Virtual Machine '$VirtualMachineName' ..."
                                #Add-AzVMDataDisk -VM $newVM -Name $disk.Name -ManagedDiskId $disk.ManagedDisk.Id -Caching $disk.Caching -Lun $disk.Lun -DiskSizeInGB $disk.DiskSizeGB -CreateOption Attach > $null
                                Add-AzVMDataDisk -VM $newVM  -ManagedDiskId $disk.ManagedDisk.Id -Lun $disk.Lun -Caching $disk.Caching -CreateOption Attach > $null
                            }    

                        }else {
                            # MOVE FROM one zone to another zone

                            # Snapshot all of the Data disks, and add to the VM
                            foreach ($disk in $originalVM.StorageProfile.DataDisks)
                            {        
                                $OriginalDataDiskName = $disk.Name
                                $DataDisk             = Get-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $OriginalDataDiskName

                                #snapshot & copy the data disk
                                #$snapshot = New-AzUniqueNameSnapshot -ResourceGroupName $VMResourceGroupName -Location $location -DiskName $disk.Name -SourceDiskResourceId $disk.ManagedDisk.Id
                                $snapshot = New-AzUniqueNameSnapshot -ResourceGroupName $DataDisk.ResourceGroupName -Location $location -Disk $DataDisk
                                
                                $diskName        = $disk.Name
                                $DataDisk        = Get-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $OriginalDataDiskName    
                                $DataDiskTags    = $DataDisk.Tags  
                                $storageType     = $DataDisk.Sku.Name

                                # Create / Copy the exsiting Data disk with a new name
                                Copy-AzUniqueNameDiskFromSnapshot -ResourceGroupName $VMResourceGroupName -Location $location -Snapshot $snapshot -OriginalDiskName $OriginalDataDiskName -StorageType $storageType -VMDiskZone $VMDiskZone -DiskNamePosfix "orig" -Tags $DataDiskTags

                                # Delete Original Data disk
                                #Write-Host
                                Write-WithTime_Using_WriteHost  "Removing original data disk '$OriginalDataDiskName' ..." 
                                Remove-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $OriginalDataDiskName -Force 
                                                                                
                                $diskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id -zone $AzureZone -Tag $DataDiskTags
                                #Write-Host
                                Write-WithTime_Using_WriteHost  "Creating zonal data disk '$diskName' from snapshot '$($snapshot.Name)' ..."
                                
                                $newdisk = New-AzDisk -Disk $diskConfig -ResourceGroupName $VMResourceGroupName -DiskName $diskName # > $null
                                
                                Write-WithTime_Using_WriteHost "Configuring data disk '$($newdisk.Name)' , LUN '$($disk.Lun)' for Virtual Machine '$VirtualMachineName' ... " 
            
                                if($disk.WriteAcceleratorEnabled) {
                                    
                                    Write-WithTime_Using_WriteHost "Adding disk '$($newdisk.Name)' to new VM with enabled Write Accelerator ... " #-AppendEmptyLine
                                    Add-AzVMDataDisk -VM $newVM -Name $newdisk.Name -ManagedDiskId $newdisk.Id -Caching $disk.Caching -Lun $disk.Lun -DiskSizeInGB $newdisk.DiskSizeGB -CreateOption Attach -WriteAccelerator  > $null    
                                }else{
                                    
                                    Write-WithTime_Using_WriteHost "Adding disk '$($newdisk.Name)' to new VM ... "
                                    Add-AzVMDataDisk -VM $newVM -Name $newdisk.Name -ManagedDiskId $newdisk.Id -Caching $disk.Caching -Lun $disk.Lun -DiskSizeInGB $newdisk.DiskSizeGB -CreateOption Attach > $null    
                                }
                            }
                        }

                        
                        # Add NIC(s) and keep the same NIC as primary
                        foreach ($nic in $originalVM.NetworkProfile.NetworkInterfaces) {                  
                            
                            Write-WithTime_Using_WriteHost "Configuring '$($nic.Id)' network card to Virtual Machine '$VirtualMachineName' ..."
                            if ($nic.Primary -eq "True"){                
                                
                                Write-WithTime_Using_WriteHost "NIC is primary."
                                Add-AzVMNetworkInterface -VM $newVM -Id $nic.Id -Primary > $null
                            }
                            else{                                            
                                Write-WithTime_Using_WriteHost "NIC is secondary."
                                Add-AzVMNetworkInterface -VM $newVM -Id $nic.Id > $null
                            }
                        }
        
                        # Copy VM Tags
                        if(-not $DoNotCopyTags){
                            # Copy the Tags
                            #Write-Host
                            Write-WithTime_Using_WriteHost "Listing VM '$VirtualMachineName' tags: "                        
                            $originalVM.Tags
                    
                            #Write-Host
                            Write-WithTime_Using_WriteHost "Copy Tags ..."
                            $newVM.Tags = $originalVM.Tags
                            #Write-Host
                            Write-WithTime_Using_WriteHost "Tags copy to new VM definition done. "
                        }else{            
                            #Write-Host
                            Write-WithTime_Using_WriteHost "Skipping copy of VM tags:"       
                            
                            $originalVM.Tags
                        }
                        
                        #Configure Boot Diagnostic account
                        if ($originalVM.DiagnosticsProfile.BootDiagnostics.Enabled) {
                            
                            Write-WithTime_Using_WriteHost "Boot diagnostic account is enabled."
                            
                            # Get Strage URI
                            $StorageUri = $originalVM.DiagnosticsProfile.BootDiagnostics.StorageUri            
                
                            if ($StorageUri -eq $null) {
                                            
                                Write-WithTime_Using_WriteHost "Boot diagnostic URI is empty."
                                
                                Write-WithTime_Using_WriteHost "Configuring boot diganostic with managed storage account ..."           

                                $newVM = Set-AzVMBootDiagnostic -VM $newVM -Enable                                                                             
                
                            }else {
                                
                                $BootDiagnosticURI = $originalVM.DiagnosticsProfile.BootDiagnostics.StorageUri.Split("/")[2]
                                
                                Write-WithTime_Using_WriteHost "Boot diagnostic URI: '$BootDiagnosticURI'."
                    
                                $staccName = $BootDiagnosticURI.Split(".")[0]
                                
                                Write-WithTime_Using_WriteHost "Extracted storage account name: '$staccName'"
                                                
                                Write-WithTime_Using_WriteHost "Getting storage account '$staccName'" #-AppendEmptyLine
                                $stacc = Get-AzStorageAccount | where-object { $_.StorageAccountName.Contains($staccName) }
                                
                                if($stacc  -eq $null ){
                                    
                                    Write-WithTime "Storage account '$staccName' used for diagonstic account on source VM do not exist. Skipping configuration of boot diagnostic on the new VM."
                                
                                }else{
            
                                    Write-WithTime_Using_WriteHost "Configuring storage account '$staccName' for VM boot diagnostigs in Azure resource group '$($stacc.ResourceGroupName)' on the new VM ..."
                                
                                    $newVM = Set-AzVMBootDiagnostic -VM $newVM -Enable -ResourceGroupName $stacc.ResourceGroupName -StorageAccountName $staccName
                                                
                                    Write-WithTime_Using_WriteHost "Configuring storage account '$staccName' for VM boot diagnostigs done." #-AppendEmptyLine
                                }
                                
                            }
                        }                         
            
                        # Create the new VM
                        Write-WithTime_Using_WriteHost "Recreating Virtual Machine '$VirtualMachineName' as zonal VM in Azure zone '$AzureZone' ..."
                        if ($VMLicenseType) {
                            New-AzVM -ResourceGroupName $VMResourceGroupName -Location $originalVM.Location -VM $newVM -DisableBginfoExtension -LicenseType $VMLicenseType
                        }else {
                            New-AzVM -ResourceGroupName $VMResourceGroupName -Location $originalVM.Location -VM $newVM -DisableBginfoExtension
                        }
                        

                        # Set back roles
                        #Set-AzVMsAndRoles -AzVMsAndRoles $ALLVMsAndRoles
                
                    #########################################
                    # move to the regional (NON-zonal) VM
                    #########################################

                    }else{
                        
                        $VMSSZones = Get-AzVMSSZones -VMSSName $VMSSName -VMSSGResourceGroupName $VMSSGResourceGroupName
                        if ($VMSSZones -ne $null) {
                            # VMSS is cinfigured for the zones, and VM goes to region - NOT supported
                            Throw "Your Virtual Macine Scale Set (VMSS) '$VMSSName' in resource group '$VMSSGResourceGroupName' is configured for zonal deployment. You chose to move VM in an non-zonal context. This is not supported. Please chose VMSS that has no configured zones, or specify parameter '-AzureZones <ZoneNumber>'."
                        }

                        if ($AzureRegionHasZones) {
                            Throw "Azure region '$($originalVM.Location)' has zones. In regional context, VMSS Flex for SAP is not supported in Azure regions which have zones. In regions with zones, VMSS Flex is supported only in zonal context e.g. VMSS Flex has to be configured with zones and VM has to be moved to zone."
                        }else {
                            Write-WithTime_Using_WriteHost "Azure region '$($originalVM.Location)' has no zones. In regional context, VMSS Flex for SAP is supported in Azure regions which have no zones."
                        }

                        # Check PPG
                        <# if ($ProximityPlacementGroupName) {
                            $ppg = Get-AzProximityPlacementGroup -ResourceGroupName $PPGResourceGroupName -Name $ProximityPlacementGroupName -ErrorAction Stop
         
                            $ProximityPlacementGroupExist = $True
             
                            Write-Host
                            Write-WithTime_Using_WriteHost "Proximity Placement Group '$ProximityPlacementGroupName' in resource group '$PPGResourceGroupName' exist."
                             
                            # Check if VMSSS is configured withthe same PPG
                            if ($VmssFlex.ProximityPlacementGroup.Id -ne $ppg.Id) {
                                Write-Host
                                Throw "Existing Virtual Machine Scale Set '$VMSSName' is not member of Proximity Placement Group '$ProximityPlacementGroupName'. Please configure Virtual Machine Scale Set '$VMSSName' to use Proximity Placement Group '$ProximityPlacementGroupName'. "
                            }else{
                                Write-WithTime_Using_WriteHost "Virtual Machine Scale Set '$VMSSName' is configured in appropriate Proximity Placement Group '$ProximityPlacementGroupName'." -PrependEmptyLine
                            }
                             
                        } #>
                                     

                        # Get confiramtion to migrate to the zone
                        if ($VMIsZonal) {
                            Write-WithTime_Using_WriteHost  "Virtual Machine '$VirtualMachineName' is zonal VM."

                            $ToContinue = $true               

                            if(-not $Force){
                                #Write-Host
                                $ToContinue = Get-Answer "Virtual Machine '$VirtualMachineName' in resource group '$VMResourceGroupName' is zonal VM. You chose to move VM to the non-zone VM. VM will be converted to the non-zonal VM. Do you agree to continue?"    
                            }
                                            
                            if(-not $ToContinue){             
                                Return   
                            }                            
                        }                    
                                                
                        [string] $osType      = $originalVM.StorageProfile.OsDisk.OsType
                        [string] $location    = $originalVM.Location
                        [string] $storageType = $originalVM.StorageProfile.OsDisk.ManagedDisk.StorageAccountType
                        [string] $OSDiskName  = $originalVM.StorageProfile.OsDisk.Name                    
                        # when non-Zonal disk / VM this value is an empty string
                        [string] $VMDiskZone  = $originalVM.Zones  
                        $OSDisk               = Get-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $OSDiskName    
                        $OSDiskTags           = $OSDisk.Tags        
                        $VMPlan                      = $originalVM.Plan
                        $VMSecurityType              = $originalVM.SecurityProfile.SecurityType
                        [bool] $VMSecureBootEnabled  = $originalVM.SecurityProfile.UefiSettings.SecureBootEnabled
                        [bool] $VMVTpmEnabled        = $originalVM.SecurityProfile.UefiSettings.VTpmEnabled
                        $VMLicenseType               = $originalVM.LicenseType

                        Write-WithTime_Using_WriteHost "Checking Hyper-V Generation of virtual machine '$VirtualMachineName' in resource group '$VMResourceGroupName' ..." 
                        $VMStatus = Get-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -Status
                        $GenType = $VMStatus.HyperVGeneration
                        if ($GenType -eq "V1") {
                            Write-WithTime_Using_WriteHost "Hyper-V Generation of virtual machine '$VirtualMachineName' in resource group '$VMResourceGroupName' is 'V1' - VM is Generation 1."
                        }elseif ($GenType -eq "V2") {
                            Write-WithTime_Using_WriteHost "Hyper-V Generation of virtual machine '$VirtualMachineName' in resource group '$VMResourceGroupName' is 'V2' - VM is Generation 2."
                        }

                        if ($VMSecurityType) {
                            Write-WithTime_Using_WriteHost "VM security type is '$VMSecurityType'."
                            Write-WithTime_Using_WriteHost "VM secure boot is set to '$VMSecureBootEnabled'."
                            Write-WithTime_Using_WriteHost "VM vTPM (Trusted Platform Module) is set to '$VMVTpmEnabled'."
                        }else {
                        Write-WithTime_Using_WriteHost "VM security type is 'Standard'."
                        }          

                        # Shutdown the original VM
                        $ToStop = $true

                        if(-not $Force){
                            #Write-Host
                            $ToStop = Get-AzVMStopAnswer -VirtualMachineName $VirtualMachineName -ResourceGroupName $VMResourceGroupName
                        }                                        

                        if($ToStop){                        
                            Write-WithTime_Using_WriteHost  "Stopping Virtual Machine '$VirtualMachineName' in resource group '$VMResourceGroupName' ..."
                            $ReturnStopVM =  Stop-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -force -ErrorAction Stop 
                            
                            Write-WithTime_Using_WriteHost  "Stopping Virtual Machine '$VirtualMachineName' in resource group '$VMResourceGroupName' stoped."
                        }elseif (!$ToStop) {                    
                            Return                 
                        }         

                        $OriginalVMSize =  $originalVM.HardwareProfile.VmSize
                        $VMSize =  $originalVM.HardwareProfile.VmSize

                        # We don't support moving machines with public IPs, since those are zone specific.
                        foreach ($nic in $originalVM.NetworkProfile.NetworkInterfaces) {
                            $thenic = $nic.id
                            $nicname = $thenic.substring($thenic.LastIndexOf("/")+1)
                            $othernic = Get-AzNetworkInterface -name $nicname -ResourceGroupName $VMResourceGroupName 

                            foreach ($ipc in $othernic.IpConfigurations) {
                                $pip = $ipc.PublicIpAddress
                                if ($pip) { 
                                    Throw  "Sorry, machines with public IPs are not supported by this script"                             
                                }
                            }
                        }                     
                               
                        # Do NOT set -PlatformFaultDomain as PlatformFaultDomainCount must be 1
                        $newVM = Set-AzVirtualMachineConfiguration -OriginalVM $originalVM -VMSize $VMSize -VmssFlex $VmssFlex 
                        

                        if ($VMSecurityType -eq "TrustedLaunch") {
                            Write-WithTime_Using_WriteHost "Configuring VM security setting to 'TrustedLaunch' ..." 
                            $NewVM = Set-AzVmSecurityProfile -VM $NewVM -SecurityType "TrustedLaunch"
                        
                            Write-WithTime_Using_WriteHost "Configuring VM secure boot '$VMSecureBootEnabled' and vTPM to '$VMVTpmEnabled' ..."
                            $NewVM = Set-AzVMUefi -VM $newVM -EnableSecureBoot $VMSecureBootEnabled -EnableVtpm $VMVTpmEnabled
                        }

                        <# if ($ProximityPlacementGroupName) {
                            # If PPG is specified
                            $newVM = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VMSize -VmssId $VmssFlex.Id -PlatformFaultDomain $FaultDomainNumber -ProximityPlacementGroupId $ppg.Id
                        }else{
                            $newVM = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VMSize -VmssId $VmssFlex.Id -PlatformFaultDomain $FaultDomainNumber
                        } #>

                        
                        # Copy VM Tags
                        if(-not $DoNotCopyTags){
                            # Copy the Tags
                            Write-WithTime_Using_WriteHost "Listing VM '$VirtualMachineName' tags: "              
                            $originalVM.Tags
                    
                            Write-WithTime_Using_WriteHost "Copy Tags ..."
                            $newVM.Tags = $originalVM.Tags
                            Write-WithTime_Using_WriteHost "Tags copy to new VM definition done. "
                        }else{            
                            Write-WithTime_Using_WriteHost "Skipping copy of VM tags:"        
                            
                            $originalVM.Tags
                        }

                        if ($VMIsZonal) {
                            # Snap and copy the os disk
                            $snapshot = New-AzUniqueNameSnapshot -ResourceGroupName $VMResourceGroupName -Location $location -DiskName $originalVM.StorageProfile.OsDisk.Name -SourceDiskResourceId $originalVM.StorageProfile.OsDisk.ManagedDisk.Id

                            # Create / Copy the exsiting OS Disk with new name as non-zonal disk
                            Copy-AzUniqueNameDiskFromSnapshot -ResourceGroupName $VMResourceGroupName -Location $location -Snapshot $snapshot -OriginalDiskName $OSDiskName -StorageType $storageType -VMDiskZone $VMDiskZone -DiskNamePosfix "orig" -Tags $OSDiskTags
                        }
                        
                        # Do not delete OS and Data disks during the VM deletion - set DeleteOption to 'Detach'
                        Set-AzVMDisksDeleteOption -VM $originalVM -DeleteOption "Detach"                                
    
                        # Do not delete NIC cards during the VM deletion - Set NIC Cards to 'Detach'
                        Set-AzVMNICsDeleteOption -VM $originalVM -DeleteOption "Detach"                
                        
                        # Remove the original VM -this is a prerequisit to delete orignial OS and data disks
                        $ToDelete = $true
    
                        if(-not $Force){
                            $ToDelete = Get-AzVMDeleteAnswer -VirtualMachineName $VirtualMachineName -ResourceGroupName $VMResourceGroupName
                        }
                        
                        if($ToDelete){
                            Write-WithTime_Using_WriteHost  "Removing Virtual Machine '$VirtualMachineName' ..."              
                            Remove-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -force            
                        }else {
                            # Exit
                            Return   
                        }              
                        
                        if ($VMIsZonal) {
                            # Delete Original OS Disk
                            Write-WithTime_Using_WriteHost  "Removing original OS disk '$osdiskname' ..."
                            Remove-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $osdiskname -Force  
                        }
                        
                        if ($VMIsZonal) {
                            # new OS disk is non-zonal
                            $newdiskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $OSDiskTags # -zone $AzureZone

                            $newdiskName = $osdiskname 
                        
                            Write-WithTime_Using_WriteHost  "Creating OS disk '$newdiskName' from snapshot '$($snapshot.Name)' ..."
                            $newdisk = New-AzDisk -Disk $newdiskConfig -ResourceGroupName $VMResourceGroupName -DiskName $newdiskName 
                        }
                                                            
                        if ($osType -eq "Linux")
                        {  
                            Write-WithTime_Using_WriteHost "Configuring Linux OS disk '$OSDiskName' .. " #-AppendEmptyLine
                            if ($VMIsZonal) {
                                Set-AzVMOSDisk -VM $newVM -CreateOption Attach  -ManagedDiskId $newdisk.Id -Name $newdisk.Name  -Linux -Caching $originalVM.StorageProfile.OsDisk.Caching > $null
                            }else{
                                Set-AzVMOSDisk  -VM $newVM -CreateOption Attach -ManagedDiskId $originalVM.StorageProfile.OsDisk.ManagedDisk.Id -Name $OSDiskName -Linux -Caching $originalVM.StorageProfile.OsDisk.Caching > $null
                            }                        
                        }elseif ($osType -eq "Windows"){                    
                            Write-WithTime_Using_WriteHost "Configuring Windows OS disk '$OSDiskName' .. "
                            if ($VMIsZonal) {
                                Set-AzVMOSDisk -VM $newVM -CreateOption Attach  -ManagedDiskId $newdisk.Id -Name $newdisk.Name  -Windows -Caching $originalVM.StorageProfile.OsDisk.Caching > $null    
                            }else{
                                Set-AzVMOSDisk  -VM $newVM -CreateOption Attach -ManagedDiskId $originalVM.StorageProfile.OsDisk.ManagedDisk.Id -Name $OSDiskName -Windows -Caching $originalVM.StorageProfile.OsDisk.Caching    > $null    
                            }                        
                        }                    

                        if ($VMIsZonal) {
                            # Snapshot all of the Data disks, and add to the VM
                            foreach ($disk in $originalVM.StorageProfile.DataDisks){
                                    
                                        $OriginalDataDiskName = $disk.Name
                                        $DataDisk             = Get-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $OriginalDataDiskName    
                                        $DataDiskTags         = $DataDisk.Tags  

                                        #snapshot & copy the data disk
                                        $snapshot = New-AzUniqueNameSnapshot -ResourceGroupName $VMResourceGroupName -Location $location -DiskName  $disk.Name -SourceDiskResourceId $disk.ManagedDisk.Id                                                               
                                    
                                        $diskName = $disk.Name

                                        # Create / Copy the exsiting Data disk with a new name
                                        Copy-AzUniqueNameDiskFromSnapshot -ResourceGroupName $VMResourceGroupName -Location $location -Snapshot $snapshot -OriginalDiskName $OriginalDataDiskName -StorageType $storageType -VMDiskZone $VMDiskZone -DiskNamePosfix "orig" -Tags $DataDiskTags

                                        # Delete Original Data disk
                                        Write-WithTime_Using_WriteHost  "Removing original data disk '$OriginalDataDiskName' ..." 
                                        Remove-AzDisk -ResourceGroupName $VMResourceGroupName -DiskName  $OriginalDataDiskName -Force 
                                                                                        
                                        $diskConfig = New-AzDiskConfig -AccountType $storageType -Location $location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $DataDiskTags #-zone $AzureZone
                                        Write-WithTime_Using_WriteHost  "Creating data disk '$diskName' from snapshot '$($snapshot.Name)' ..."
                                        
                                        $newdisk = New-AzDisk -Disk $diskConfig -ResourceGroupName $VMResourceGroupName -DiskName $diskName # > $null
                                        
                                        Write-WithTime_Using_WriteHost "Configuring data disk '$($newdisk.Name)' , LUN '$($disk.Lun)' for Virtual Machine '$VirtualMachineName' ... " 
                    
                                        if($disk.WriteAcceleratorEnabled) {
                                            
                                            Write-WithTime_Using_WriteHost "Adding disk '$($newdisk.Name)' to new VM with enabled Write Accelerator ... "
                                            Add-AzVMDataDisk -VM $newVM -Name $newdisk.Name -ManagedDiskId $newdisk.Id -Caching $disk.Caching -Lun $disk.Lun -DiskSizeInGB $newdisk.DiskSizeGB -CreateOption Attach -WriteAccelerator  > $null    
                                        }else{
                                            
                                            Write-WithTime_Using_WriteHost "Adding disk '$($newdisk.Name)' to new VM ... "
                                            Add-AzVMDataDisk -VM $newVM -Name $newdisk.Name -ManagedDiskId $newdisk.Id -Caching $disk.Caching -Lun $disk.Lun -DiskSizeInGB $newdisk.DiskSizeGB -CreateOption Attach > $null    
                                        }
                            }                          
                        }else{
                            # Add exisiting Data Disks
                            foreach ($disk in $originalVM.StorageProfile.DataDisks) { 
                                # Write-Host
                                Write-WithTime_Using_WriteHost "Adding data disk '$($disk.Name)' to Virtual Machine '$VirtualMachineName' ..."
                                Add-AzVMDataDisk -VM $newVM -Name $disk.Name -ManagedDiskId $disk.ManagedDisk.Id -Caching $disk.Caching -Lun $disk.Lun -DiskSizeInGB $disk.DiskSizeGB -CreateOption Attach > $null
                            }          
                        }
                        
                        # Add NIC(s) and keep the same NIC as primary
                        foreach ($nic in $originalVM.NetworkProfile.NetworkInterfaces) {                                          
                            Write-WithTime_Using_WriteHost "Adding '$($nic.Id)' network card to Virtual Machine '$VirtualMachineName' ..."
                            if ($nic.Primary -eq "True"){                
                            Add-AzVMNetworkInterface -VM $newVM -Id $nic.Id -Primary > $null
                                }
                                else{                
                                Add-AzVMNetworkInterface -VM $newVM -Id $nic.Id > $null
                            }
                        }

                        # Configuring Boot Diagnostics
                        if ($originalVM.DiagnosticsProfile.BootDiagnostics.Enabled) {
                                                    
                            Write-WithTime_Using_WriteHost "Boot diagnostic account is enabled."
                            
                            # Get Strage URI
                            $StorageUri = $originalVM.DiagnosticsProfile.BootDiagnostics.StorageUri 
                            
                            if ($StorageUri -eq $null) {

                                Write-WithTime_Using_WriteHost "Boot diagnostic URI is empty."
                                
                                Write-WithTime_Using_WriteHost "Configuring boot diganostic with managed storage account ..."      

                                $newVM = Set-AzVMBootDiagnostic -VM $newVM -Enable       

                            }else {
                                
                                $BootDiagnosticURI = $originalVM.DiagnosticsProfile.BootDiagnostics.StorageUri.Split("/")[2]
                                
                                Write-WithTime_Using_WriteHost "Boot diagnostic URI: '$BootDiagnosticURI'."

                                $staccName = $BootDiagnosticURI.Split(".")[0]
                                
                                Write-WithTime_Using_WriteHost "Extracted storage account name: '$staccName'"
                                
                                Write-WithTime_Using_WriteHost "Getting storage account '$staccName'"
                                $stacc = Get-AzStorageAccount | where-object { $_.StorageAccountName.Contains($staccName) }
                                
                                if($stacc  -eq $null ){                                
                                    Write-WithTime_Using_WriteHost "Storage account '$staccName' used for diagonstic account on source VM do not exist. Skipping configuration of boot diagnostic on the new VM." # -AppendEmptyLine
                                
                                }else{

                                    Write-WithTime_Using_WriteHost "Configuring storage account '$staccName' for VM boot diagnostigs in Azure resource group '$($stacc.ResourceGroupName)' on the new VM ..." # -AppendEmptyLine
                                
                                    $newVM = Set-AzVMBootDiagnostic -VM $newVM -Enable -ResourceGroupName $stacc.ResourceGroupName -StorageAccountName $staccName
                                    
                                    Write-WithTime_Using_WriteHost "Configuring storage account '$staccName' for VM boot diagnostigs done."
                                }
                                
                            }                    
                        }                                    
                                            
                        Write-WithTime_Using_WriteHost "Recreating the '$VirtualMachineName' VM and adding to the Virtual Machine Scale Set '$VMSSName' ..."

                        if ($VMLicenseType) {
                            New-AzVM -ResourceGroupName $VMResourceGroupName -Location $originalVM.Location -VM $newVM -ErrorAction Stop -LicenseType $VMLicenseType
                        }else {
                            New-AzVM -ResourceGroupName $VMResourceGroupName -Location $originalVM.Location -VM $newVM -ErrorAction Stop
                        }

                        # Set back roles
                        #Set-AzVMsAndRoles -AzVMsAndRoles $ALLVMsAndRoles
                    }
            
                    Write-WithTime_Using_WriteHost "Done!"
                
                }
                catch{
                   Write-Error  $_.Exception.Message           
               }
            }
        
            END {}
}



function Set-AzVMsAndRoles {
    <#
    .SYNOPSIS
    Set-AzVMsAndRoles sets roles for all passed VMs, and roles.
     
    .DESCRIPTION
         
    .PARAMETER RoleAssignment
     
 
    .EXAMPLE
         
     
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
    #>
    
    
        [CmdletBinding()]
        param(

        [Parameter(Mandatory=$false,ValueFromPipeline=$True)]               
        $AzVMsAndRoles

        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            # try{

                Write-Verbose "Executing Set-AzVMsAndRoles ..."

                foreach ($AzVMAndRole in $AzVMsAndRoles) {
                    Write-Verbose "Setting role with definition name '$($AzVMAndRole.Role.RoleDefinitionName)' and scope '$($AzVMAndRole.Role.Scope)' ,for VM '$($AzVMAndRole.VMName)' in resource group '$($AzVMAndRole.VMResourceGroupName)' ..."
                                
                    Set-AzVMIdentityRole -VMResourceGroupName $AzVMAndRole.VMResourceGroupName -VirtualMachineName $AzVMAndRole.VMName -RoleAssignment $AzVMAndRole.Role
                }

                Write-Verbose "Set-AzVMsAndRoles executed succesfully."

            #}
            #catch{
            # Write-Error "Error while executing Set-AzVMsAndRoles. $_.Exception.Message"
            #}
    
        }
    
        END {}
}


function Get-AzVMsAndRoles {
    <#
    .SYNOPSIS
    Get-AzVMsAndRoles connects to one VM. From VM Roles, it gets list of ALL VMs associated with role, and collect for all VMs roles.
     
    .DESCRIPTION
         
    .PARAMETER RoleAssignment
     
 
    .EXAMPLE
         
     
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
    #>
    
    
        [CmdletBinding()]
        param(
                        
        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]               
        [string] $VMResourceGroupName,
        
        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]               
        [string] $VirtualMachineName

        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            # try{

                Write-Verbose "Executing Get-AzVMsAndRoles ..."

                $VM = Get-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -ErrorAction Stop

                if ($VM.Identity.Type -ne "UserAssigned") {
                    $VMPrincipalId = $VM.Identity.PrincipalId

                    $VMRoles = Get-AzRoleAssignment -ObjectId $VMPrincipalId

                    # if we have roles
                    if ($VMRoles) {
                        $AllVMs = Get-AzVMsFromVMRoles -RoleAssignment $VMRoles

                        # Loop throuhgh ALL VMs
                        foreach ($OneVM in $AllVMs) {
                            $OneVMRoles = Get-AzRoleAssignment -ObjectId $OneVM.Identity.PrincipalId

                            # Loop for each VM all roles
                            foreach ($OneVMRole in $OneVMRoles) {

                                $obj = New-Object -TypeName psobject

                                #$VMResource = Get-AzResource -ResourceId $Role.Scope
                                $VMName                 = $OneVM.VMName
                                $VMResourceGroupName    = $OneVM.VMResourceGroupName
                                $VMLocation             = $OneVM.VMLocation
                                $ResourceId             = $OneVM.ResourceId
                                $Identity               = $OneVM.Identity
                                $Role                   = $OneVMRole

                                Write-WithTime_Using_WriteHost "Getting role with definition name '$($Role.RoleDefinitionName)' and scope '$($Role.Scope)' ,for VM '$VMName' in resource group '$VMResourceGroupName' ..."
                                
                                $obj | add-member  -NotePropertyName "VMName"               -NotePropertyValue $VMName 
                                $obj | add-member  -NotePropertyName "VMResourceGroupName"  -NotePropertyValue $VMResourceGroupName
                                $obj | add-member  -NotePropertyName "VMLocation"           -NotePropertyValue $VMLocation
                                $obj | add-member  -NotePropertyName "ResourceId"           -NotePropertyValue $ResourceId
                                $obj | add-member  -NotePropertyName "Identity"             -NotePropertyValue $Identity
                                $obj | add-member  -NotePropertyName "Role"                 -NotePropertyValue $Role

                                #Return formated object
                                Write-Output $obj    
            
                            }
                        }
                    }
                }else {
                    Write-WithTime_Using_WriteHost("VM '$VirtualMachineName' in resource group '$VMResourceGroupName' has identity type 'UserAssigned'. There are no managed identity roles.")
                }

                Write-Verbose "Get-AzVMsAndRoles executed succesfully."

            #}
            #catch{
            # Write-Error "Error while executing Get-AzVMsAndRoles. $_.Exception.Message"
            #}
    
        }
    
        END {}
}


function Get-AzVMsFromVMRoles {
    <#
    .SYNOPSIS
               
     
    .DESCRIPTION
    Get-AzVMsFromVMRoles retruf list of ALL VMs filtered from RoleAssignment.
 
    .PARAMETER RoleAssignment
    Get-AzVMsFromVMRoles retruf list of ALL VMs filtered from RoleAssignment. it returns objects with properties 'VMName', 'VMResourceGroupName', 'VMLocation', 'ResourceId', 'Identity'
 
    .EXAMPLE
         
     
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
    #>
    
    
        [CmdletBinding()]
        param(
                        
        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]     
        $RoleAssignment

        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   

                Write-Verbose "Executing Get-AzVFromVMRoles ..."
                                
                foreach ($Role in $RoleAssignment) {
                    
                    $obj = New-Object -TypeName psobject

                    $VMResource = Get-AzResource -ResourceId  $Role.Scope
                    $VMName                 = $VMResource.Name
                    $VMResourceGroupName    = $VMResource.ResourceGroupName
                    $VMLocation             = $VMResource.Location
                    $ResourceId             = $VMResource.ResourceId
                    $Identity               = $VMResource.Identity
                    
                    $obj | add-member  -NotePropertyName "VMName"               -NotePropertyValue $VMName 
                    $obj | add-member  -NotePropertyName "VMResourceGroupName"  -NotePropertyValue $VMResourceGroupName
                    $obj | add-member  -NotePropertyName "VMLocation"           -NotePropertyValue $VMLocation
                    $obj | add-member  -NotePropertyName "ResourceId"           -NotePropertyValue $ResourceId
                    $obj | add-member  -NotePropertyName "Identity"             -NotePropertyValue $Identity

                    #Return formated object
                    Write-Output $obj      
                }  
                
                Write-Verbose "Get-AzVFromVMRoles executed succesfully."

            }
            catch{
                Write-Error "Error while executing Get-AzVFromVMRoles. $_.Exception.Message"               
            }
    
        }
    
        END {}
}


function Set-AzVMIdentityRole {
    <#
    .SYNOPSIS
    Set-AzVMIdentityRole sets on ONE VM (non UserAssigned) managed identity security role.
 
    .DESCRIPTION
    Set-AzVMIdentityRole sets on ONE VM (non UserAssigned) managed identity security role.
         
    .EXAMPLE
     
 
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    [CmdletBinding()]
    param(
     
        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]               
        [string] $VMResourceGroupName,
        
        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]               
        [string] $VirtualMachineName,
        
        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]     
        $RoleAssignment

       )
    
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                
                Write-Verbose "Executing Set-AzVMIdentityRole ..."

                $VM = Get-AzVM -ResourceGroupName $VMResourceGroupName -Name $VirtualMachineName -ErrorAction Stop

                if ($VM.Identity.Type -ne "UserAssigned") {
                    $VMPrincipalId = $VM.Identity.PrincipalId

                    foreach ($Role in $RoleAssignment) {
                        Write-WithTime_Using_WriteHost("Setting new role assigment on VM '$VirtualMachineName' in resource group '$VMResourceGroupName' with VM Principal Id '$VMPrincipalId', role definition '$($Role.RoleDefinitionName)', scope '$($Role.scope)' ...")
                        New-AzRoleAssignment -ObjectId $VMPrincipalId -RoleDefinitionName  $Role.RoleDefinitionName -Scope $Role.scope -ErrorAction SilentlyContinue
                    }
                }else {
                    Write-WithTime_Using_WriteHost("VM '$VirtualMachineName' in resource group '$VMResourceGroupName' has identity type 'UserAssigned'. It is not possible to set roles for managed identity.")
                }
                
                Write-Verbose "Set-AzVMIdentityRole executed succesfully."
            }
            catch{                 
                Write-Error "Error while executing Set-AzVMIdentityRole. $_.Exception.Message"         
            }
        }
    
        END {}
}


function Set-AzVirtualMachineConfiguration {
    <#
    .SYNOPSIS
    Set-AzVirtualMachineConfiguration is generic function to create new VM configuration.
 
    .DESCRIPTION
    Set-AzVirtualMachineConfiguration is generic function to create new VM configuration.
         
    .EXAMPLE
     
 
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    [CmdletBinding()]
    param(
     
        [Parameter(Mandatory = $True, ParameterSetName="Regional")]
        [Parameter(Mandatory = $True, ParameterSetName="Zonal")]   
        #[Parameter(ParameterSetName="VmssFlex")]
        $OriginalVM,

        [Parameter(Mandatory = $True, ParameterSetName="Regional")]
        [Parameter(Mandatory = $True, ParameterSetName="Zonal")]    
        #[Parameter(ParameterSetName="VmssFlex")]
        [string] $VMSize,

        [Parameter(Mandatory = $True, ParameterSetName="Zonal")]     
        #[Parameter(ParameterSetName="VmssFlex")]
        [ValidateRange(1,3)]
        [int] $AzureZone,

        [Parameter(Mandatory = $True, ParameterSetName="Regional")]
        [Parameter(Mandatory = $True, ParameterSetName="Zonal")]        
        #[Parameter(ParameterSetName="VmssFlex")]
        $VmssFlex
        
    )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                
                Write-Verbose "Executing Set-AzVirtualMachineConfiguration ..."

                $VMIdentity = $originalVM.Identity
                $VirtualMachineName = $OriginalVM.Name

                switch ($PSCmdlet.ParameterSetName) {
                    "Zonal" {    
                        Write-Verbose "Inside ZONAL Set-AzVirtualMachineConfiguration ..."

                        if ($VMIdentity.Type -eq "SystemAssigned") {
                            Write-WithTime_Using_WriteHost "Configuring Virtual Machine identity to 'SystemAssigned' ..."
                            #$newVM = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VMSize -VmssId $VmssFlex.Id -Zone $AzureZone -IdentityType "SystemAssigned"
                            $newVMConfig = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VMSize  -VmssId $VmssFlex.Id -Zone $AzureZone -IdentityType "SystemAssigned"    
                        }elseif ($VMIdentity.Type -eq "SystemAssignedUserAssigned") {
                            Write-WithTime_Using_WriteHost "Configuring Virtual Machine identity to 'SystemAssignedUserAssigned' ..."
                            #$newVM = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VMSize -VmssId $VmssFlex.Id -Zone $AzureZone -IdentityType "SystemAssignedUserAssigned" -IdentityId $originalVM.Identity.UserAssignedIdentities.Keys
                            $newVMConfig = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VMSize  -VmssId $VmssFlex.Id -Zone $AzureZone -IdentityType "SystemAssignedUserAssigned" -IdentityId $originalVM.Identity.UserAssignedIdentities.Keys
                            
                        } elseif ($VMIdentity.Type -eq "UserAssigned"){
                            Write-WithTime_Using_WriteHost "Configuring Virtual Machine identity to 'UserAssigned' ..."
                            #$newVM = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VMSize -VmssId $VmssFlex.Id -Zone $AzureZone -IdentityType "UserAssigned" -IdentityId $originalVM.Identity.UserAssignedIdentities.Keys -ErrorAction Stop
                            $newVMConfig = New-AzVMConfig -VMName  $VirtualMachineName -VMSize $VMSize  -VmssId $VmssFlex.Id -Zone $AzureZone -IdentityType "UserAssigned" -IdentityId $originalVM.Identity.UserAssignedIdentities.Keys -ErrorAction Stop

                        }else{
                            Write-WithTime_Using_WriteHost "Configuring default Virtual Machine identity ..."
                            $newVMConfig = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VMSize  -VmssId $VmssFlex.Id -Zone $AzureZone
                        }
                
                    }
                    "Regional" {                    
                         # NO zone
                        Write-Verbose "Inside NON ZONAL Set-AzVirtualMachineConfiguration ..."

                        if ($VMIdentity.Type -eq "SystemAssigned") {
                            Write-WithTime_Using_WriteHost "Configuring Virtual Machine identity to 'SystemAssigned' ..."
                            $newVMConfig = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VMSize  -VmssId $VmssFlex.Id -IdentityType "SystemAssigned"    
                        }elseif ($VMIdentity.Type -eq "SystemAssignedUserAssigned") {
                            Write-WithTime_Using_WriteHost "Configuring Virtual Machine identity to 'SystemAssignedUserAssigned' ..."
                            $newVMConfig = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VMSize  -VmssId $VmssFlex.Id  -IdentityType "SystemAssignedUserAssigned" -IdentityId $originalVM.Identity.UserAssignedIdentities.Keys
                            
                        } elseif ($VMIdentity.Type -eq "UserAssigned"){
                            Write-WithTime_Using_WriteHost "Configuring Virtual Machine identity to 'UserAssigned' ..."
                            $newVMConfig = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VMSize  -VmssId $VmssFlex.Id -IdentityType "UserAssigned" -IdentityId $originalVM.Identity.UserAssignedIdentities.Keys -ErrorAction Stop

                        }else{
                            Write-WithTime_Using_WriteHost "Configuring default Virtual Machine identity ..."
                            $newVMConfig = New-AzVMConfig -VMName $VirtualMachineName -VMSize $VMSize  -VmssId $VmssFlex.Id 
                        }
                    }
                }                                    
                
                Write-Output $newVMConfig

                Write-Verbose "Set-AzVirtualMachineConfiguration executed succesfully."
            }
            catch{                 
                Write-Error "Error while executing Set-AzVirtualMachineConfiguration. $_.Exception.Message"         
            }
    
        }
    
        END {}
}


function Set-AzVMDisksDeleteOption {
    <#
    .SYNOPSIS
     
     
    .DESCRIPTION
     
     
    .PARAMETER Message
     
     
    .PARAMETER Level
     
     
    .PARAMETER Colour
     
     
    .EXAMPLE
     
 #>
 
    
    [CmdletBinding()]
    param(            
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        $VM,    
                  
        [string] $DeleteOption = "Detach"        
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                      
            # Set OS Disk to # Set OS Disk to '"Detach"'
            $VM.StorageProfile.OsDisk.DeleteOption = $DeleteOption
            
            Write-WithTime_Using_WriteHost "Setting OS disk to 'DeleteOption' to '$DeleteOption' ..."

            # Set Data Disks to 'Detach'
            foreach ($disk in $VM.StorageProfile.DataDisks) {                 
                Write-WithTime_Using_WriteHost "Setting data disk '$($disk.Name)' 'DeleteOption' to '$DeleteOption' ..."
                $disk.DeleteOption = $DeleteOption            
            }     
            
            Write-WithTime_Using_WriteHost "Updating VM with new disk 'DeleteOption' settings ..."
            
            Update-AzVM -ResourceGroupName $VM.ResourceGroupName -VM $VM -ErrorAction Stop > $null

        }
        catch {
            Write-Error  $_.Exception.Message
        }
    
    }
    
    END {}
}


function Set-AzVMNICsDeleteOption {
    <#
    .SYNOPSIS
     
     
    .DESCRIPTION
     
     
    .PARAMETER Message
     
     
    .PARAMETER Level
     
     
    .PARAMETER Colour
     
     
    .EXAMPLE
     
 #>
 
    
    [CmdletBinding()]
    param(            
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        $VM,          
                  
        [string] $DeleteOption = "Detach"        
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                      
            foreach ($nic in $VM.NetworkProfile.NetworkInterfaces) {                                  
                Write-WithTime_Using_WriteHost "Setting '$($nic.Id)' network card 'DeleteOption' to '$DeleteOption' ..."
                $nic.DeleteOption = "Detach"
            }
                        
            Write-WithTime_Using_WriteHost "Updating VM with new NICs 'DeleteOption' settings ..." 
               
            Update-AzVM -ResourceGroupName $VM.ResourceGroupName -VM $VM -ErrorAction Stop > $null

        }
        catch {
            Write-Error  $_.Exception.Message
        }    
    }
    
    END {}
}

function Test-AzVMSSIsFlexType {
    <#
    .SYNOPSIS
     
     
    .DESCRIPTION
     
    .EXAMPLE
     
 #>
 
    
    [CmdletBinding()]
    param(            

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMSSName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMSSGResourceGroupName
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                    
            $VMSS = Get-AzVmss -ResourceGroupName $VMSSGResourceGroupName -VMScaleSetName $VMSSName -ErrorAction Stop

            if ("Flexible" -eq $VMSS.OrchestrationMode  ) {
                return $True
            }  else {
                return $false
            }
            

        }
        catch {
            Write-Error  $_.Exception.Message
        }    
    }
    
    END {}
}


function Get-AzVMSSZones {
    <#
    .SYNOPSIS
     
     
    .DESCRIPTION
     
     
    .PARAMETER Message
     
     
    .PARAMETER Level
     
     
    .PARAMETER Colour
     
     
    .EXAMPLE
     
 #>
 
    
    [CmdletBinding()]
    param(            

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMSSName,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VMSSGResourceGroupName
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                    
            $VMSS = Get-AzVmss -ResourceGroupName $VMSSGResourceGroupName -VMScaleSetName $VMSSName -ErrorAction Stop

            $Zones = $VMSS.Zones

            return $Zones

        }
        catch {
            Write-Error  $_.Exception.Message
        }    
    }
    
    END {}
}

function Test-AzVMSKUZonalAvailability {
    <#
    .SYNOPSIS
    Test-AzVMSKUZonalAvailability ckeck if VM SKU is avaible in the specifed zone.
     
    .DESCRIPTION
    Test-AzVMSKUZonalAvailability returns $true if VM SKU is avaibel in the specifed zone, othewise returns $false and writes an error.
     
    .PARAMETER VMSize
    VM size.
     
    .PARAMETER location
    Azure region.
     
    .PARAMETER AzureZone
    Azure zone number.
     
     
    .EXAMPLE
     
 #>
 
    
    [CmdletBinding()]
    param(            
        [Parameter(Mandatory)]        
        [string] $VMSize,
    
        [Parameter(Mandatory)]
        [string] $location,

        [Parameter(Mandatory)]
        [string] $AzureZone
            
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                                  
            Write-WithTime_Using_WriteHost "Checking VM SKU '$VMSize' availablity in Azure zone '$AzureZone' in region '$location' ...." #-AppendEmptyLine

            $VMSKUIsAvailableinAzureZone = Test-AzComputeSKUZonesAvailability -Location $location  -VMSKU $VMSize -AzureZone $AzureZone
            if (-not $VMSKUIsAvailableinAzureZone) {
                $AzureZones = Get-AzComputeSKUZonesAvailability -Location $location -VMSKU $VMSize
                if ($null -eq $AzureZones) {
                    Write-Host
                    Write-Error "VM SKU '$VMSize' is not available in any of the Azure zones in region '$location'. PLease use another VM SKU."   
                    return $false                         
                }else {
                    
                    Write-WithTime_Using_WriteHost "VM SKU '$VMSize' is available in these Azure zone(s):"
                    Write-Host $AzureZones
                    Write-Host
                    Write-Error "VM SKU '$VMSize' is not available in desired Azure zone '$AzureZone' in region '$location'. PLease use another VM SKU or another zone."  
                    return $false
                }                        
            }         

            return $true                        

        }
        catch {
            Write-Error  $_.Exception.Message
        }
    
    }
    
    END {}
}

function Get-AzVMNetworkInterfacesCards {
    <#
    .SYNOPSIS
    Get-AzVMNetworkInterfacesCards gest virtual machine all network cards
     
    .DESCRIPTION
     Get-AzVMNetworkInterfacesCards gest virtual machine all network cards
     
    .PARAMETER VMResourceGroupName
    VM Resource Group Name.
     
    .PARAMETER VirtualMachineName
    Virtual Machine Name.
     
     
    .EXAMPLE
    $NICs = Get-AzVMNetworkInterfacesCards -VMResourceGroupName gor-vmssflex1 -VirtualMachineName test1
 
    # Get NIC 0 Id
    $NICs[0].Id
 
    # Get NIC 1 Id
    $NICs[1].Id
     
 #>
 
    
    [CmdletBinding()]
    param(       
        [Parameter(Mandatory)]    
        [string] $VMResourceGroupName,
                                  
        [Parameter(Mandatory)]    
        [string] $VirtualMachineName
            
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                                  
            #Write-WithTime_Using_WriteHost "Checking VM SKU '$VMSize' availablity in Azure zone '$AzureZone' in region '$location' ...." -AppendEmptyLine

            $VM = Get-AzVM -resourceGroup $VMResourceGroupName -Name $VirtualMachineName       

            return $VM.NetworkProfile.NetworkInterfaces            

        }
        catch {
            Write-Error  $_.Exception.Message
        }
    
    }
    
    END {}
}


function Get-AzVMNetworkInterfacesCardLoadBalancerBackendAddressPools {
    <#
    .SYNOPSIS
    Get-AzVMNetworkInterfacesCards gest virtual machine all network cards
     
    .DESCRIPTION
     Get-AzVMNetworkInterfacesCards gest virtual machine all network cards
     
    .PARAMETER VMResourceGroupName
    VM Resource Group Name.
     
    .PARAMETER VirtualMachineName
    Virtual Machine Name.
     
     
    .EXAMPLE
    $NICs = Get-AzVMNetworkInterfacesCards -VMResourceGroupName gor-vmssflex1 -VirtualMachineName test1
 
    # Get NIC 0 Id
    $NICs[0].Id
 
    # Get NIC 1 Id
    $NICs[1].Id
     
 #>
 
    
    [CmdletBinding()]
    param(       
        [Parameter(Mandatory)]    
        [string] $NetworkInterfaceCardId                                          
            
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                                  
            #Write-WithTime_Using_WriteHost "Checking VM SKU '$VMSize' availablity in Azure zone '$AzureZone' in region '$location' ...." -AppendEmptyLine

            $NIC = Get-AzNetworkInterface -ResourceId $NetworkInterfaceCardId 
                        
            return $NIC.IpConfigurations.LoadBalancerBackendAddressPools

        }
        catch {
            Write-Error  $_.Exception.Message
        }
    
    }
    
    END {}
}



function Get-AzLoadbBalancerIdFromBackendPoolId {
    <#
    .SYNOPSIS
     
     
    .DESCRIPTION
     
     
    .PARAMETER VMResourceGroupName
     
     
    .PARAMETER VirtualMachineName
     
     
     
    .EXAMPLE
     
 #>
 
    
    [CmdletBinding()]
    param(       
        [Parameter(Mandatory)]    
        [string] $LoadBalancerBackendAddressPoolId
                                              
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                                  
            #Write-WithTime_Using_WriteHost "Checking VM SKU '$VMSize' availablity in Azure zone '$AzureZone' in region '$location' ...." -AppendEmptyLine

            $LoadbBalancerId=  $LoadBalancerBackendAddressPoolId -replace '/backendAddressPools/.+', ''   

            return $LoadbBalancerId           

        }
        catch {
            Write-Error  $_.Exception.Message
        }
    
    }
    
    END {}
}



function Test-AzLoadbBalancerIsStandard {
    <#
    .SYNOPSIS
     
     
    .DESCRIPTION
     
     
    .PARAMETER VMResourceGroupName
     
     
    .PARAMETER VirtualMachineName
     
     
     
    .EXAMPLE
     
 #>
 
    
    [CmdletBinding()]
    param(       
        [Parameter(Mandatory)]    
        [string] $LoadBalancerId
                                              
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                                  
            #Write-WithTime_Using_WriteHost "Checking VM SKU '$VMSize' availablity in Azure zone '$AzureZone' in region '$location' ...." -AppendEmptyLine

            $LoadBalancerResource = Get-AzResource -ResourceId $LoadBalancerId -ErrorAction Stop

            $ILB = Get-AzLoadBalancer -ResourceGroupName $LoadBalancerResource.ResourceGroupName -Name $LoadBalancerResource.Name -ErrorAction Stop

            #check if ILB SKU for 'Standard'
            if($ILB.Sku.Name -eq "Standard"){
                return $True
            }
            else{
                return $False
            }
       

        }
        catch {
            Write-Error  $_.Exception.Message
        }
    
    }
    
    END {}
}

function Test-AzVMLoadbBalancerIsStandard {
    <#
    .SYNOPSIS
     
     
    .DESCRIPTION
     
     
    .PARAMETER VMResourceGroupName
     
     
    .PARAMETER VirtualMachineName
     
     
     
    .EXAMPLE
     
 #>
 
    
    [CmdletBinding()]
    param(       
        [Parameter(Mandatory)]    
        [string] $VMResourceGroupName,

        [Parameter(Mandatory)]    
        [string] $VirtualMachineName
                                              
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                                  
            Write-WithTime_Using_WriteHost "Checking if VM SKU '$VirtualMachineName' in resource group '$VMResourceGroupName' is using standard load balancer ...." # -AppendEmptyLine
            
            $NICs = Get-AzVMNetworkInterfacesCards -VMResourceGroupName $VMResourceGroupName -VirtualMachineName $VirtualMachineName

            foreach ($nic in $NICs) {
                Write-WithTime_Using_WriteHost "Checking if NIC card '$($nic.Id)' has configured load balancer backend address pools ...." # -AppendEmptyLine

                $NICBackendLBPools = Get-AzVMNetworkInterfacesCardLoadBalancerBackendAddressPools -NetworkInterfaceCardId $nic.Id

                if($null -eq $NICBackendLBPools){
                    Write-WithTime_Using_WriteHost "NIC card '$($nic.Id)' has no configured load balancer backend address pool and no configured Azure load balancer." # -AppendEmptyLine
                }

                foreach($NICBackendLBPool in $NICBackendLBPools){
                    Write-WithTime_Using_WriteHost "NIC card '$($nic.Id)' has configured load balancer backend address pool '$($NICBackendLBPool.Id)'." # -AppendEmptyLine
                    
                    $AzureLoadBalancerId = Get-AzLoadbBalancerIdFromBackendPoolId -LoadBalancerBackendAddressPoolId $NICBackendLBPool.Id

                    Write-WithTime_Using_WriteHost "Backend address pool '$($NICBackendLBPool.Id)' is configured with Azure Load Balancer '$AzureLoadBalancerId'." # -AppendEmptyLine
                    
                    # Check is Azure load balancer is Standart type
                    $LoadBalancerIsStandardType = Test-AzLoadbBalancerIsStandard -LoadBalancerId $AzureLoadBalancerId

                    if ($LoadBalancerIsStandardType) {
                        Write-WithTime_Using_WriteHost  "Azure Load Balancer '$AzureLoadBalancerId' is Standard type. This is supported for Azure Virtual Machine Scale Set. " # -AppendEmptyLine
                    }else{
                        Write-Error "Azure Load Balancer '$AzureLoadBalancerId' is not Standard type. "
                        return $false
                    }
                }
            }
            
            return $true

        }
        catch {
            Write-Error  $_.Exception.Message
        }
    
    }
    
    END {}
}

################

function Get-AzVMLoadbBalancer {
    <#
    .SYNOPSIS
    If an VM NIC card is configured with load balancer, return Load Balanver object.
     
    .DESCRIPTION
     
     
    .PARAMETER VMResourceGroupName
     
     
    .PARAMETER VirtualMachineName
     
     
     
    .EXAMPLE
     
 #>
 
    
    [CmdletBinding()]
    param(       
        [Parameter(Mandatory)]    
        [string] $VMResourceGroupName,

        [Parameter(Mandatory)]    
        [string] $VirtualMachineName
                                              
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                                  
            Write-Verbose "Checking if VM SKU '$VirtualMachineName' in resource group '$VMResourceGroupName' is using an load balancer ...." 
            
            $NICs = Get-AzVMNetworkInterfacesCards -VMResourceGroupName $VMResourceGroupName -VirtualMachineName $VirtualMachineName

            foreach ($nic in $NICs) {
                Write-Verbose "Checking if NIC card '$($nic.Id)' has configured load balancer backend address pools ...."

                $NICBackendLBPools = Get-AzVMNetworkInterfacesCardLoadBalancerBackendAddressPools -NetworkInterfaceCardId $nic.Id

                if($null -eq $NICBackendLBPools){
                    Write-Verbose "NIC card '$($nic.Id)' has no configured load balancer backend address pool and no configured Azure load balancer."
                }

                foreach($NICBackendLBPool in $NICBackendLBPools){
                    Write-Verbose "NIC card '$($nic.Id)' has configured load balancer backend address pool '$($NICBackendLBPool.Id)'."
                    
                    $AzureLoadBalancerId = Get-AzLoadbBalancerIdFromBackendPoolId -LoadBalancerBackendAddressPoolId $NICBackendLBPool.Id

                    Write-Verbose "Backend address pool '$($NICBackendLBPool.Id)' is configured with Azure Load Balancer '$AzureLoadBalancerId'."

                    $LoadBalancerResource = Get-AzResource -ResourceId $AzureLoadBalancerId

                    $ILB = Get-AzLoadBalancer -ResourceGroupName $LoadBalancerResource.ResourceGroupName -Name $LoadBalancerResource.Name -ErrorAction Stop

                    return $ILB                                        
                }
            }
            
            return

        }
        catch {
            Write-Error  $_.Exception.Message
        }
    
    }
    
    END {}
}


###########################################
function Get-AzResourceTag {
    <#
    .SYNOPSIS
        
    .DESCRIPTION
         
    .EXAMPLE
     
    .LINK
         
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
        [CmdletBinding()]
        param(   
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $ResourceID
        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                $Tags = (Get-AzResource  -ResourceId $ResourceID).Tags
                
                $Tags
               
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}

function Set-AzResourceTag {
    <#
    .SYNOPSIS
        
    .DESCRIPTION
         
    .EXAMPLE
     
    .LINK
         
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
    
        [CmdletBinding()]
        param(   
            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            [string] $ResourceID,

            [Parameter(Mandatory=$True)]
            [ValidateNotNullOrEmpty()]        
            $Tags
        )
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                Set-AzResource  -ResourceId $ResourceID  -Tag $Tags -force
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}

function Get-AzSubscriptionContext {
    <#
    .SYNOPSIS
        
    .DESCRIPTION
         
    .EXAMPLE
     
    .LINK
         
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
            
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                $AzureContext = Get-AzContext

                $AzureSubscriptionContext = $AzureContext.Subscription

                return $AzureSubscriptionContext
            }
            catch{
                Write-Error  $_.Exception.Message           
            }
    
        }
    
        END {}
}

function Get-AzNetAppFilesAccountsPoolsVolumes {
    <#
    .SYNOPSIS
    Get-AzNetAppFilesAccountsPoolsVolumes collect information on ALL subscription Azure NetApp Accounts, its pools and volumes.
 
    .DESCRIPTION
    Get-AzNetAppFilesAccountsPoolsVolumes collect information on ALL subscription Azure NetApp Accounts, its Pools and volumes.
    It expands ANF Accoutns object with Pools, and Pools with own Volumes.
         
    .EXAMPLE
    # Colelct ALL subscription ANF accounts, with correcponding pools and volumes
    $ANFAccounts = Get-AzNetAppFilesAccountsPoolsVolumes
     
    # Get first account
    $ANFAccounts[0]
     
    # Get $ANFAccounts[0] all pools
    $ANFAccounts[0].Pools
     
    # Get $ANFAccounts[0] first pool
    $ANFAccounts[0].Pools[0]
 
    # Get $ANFAccounts[0] second pool
    $ANFAccounts[0].Pools[1]
     
    # Get $ANFAccounts[0] all volumes of the first pool
    $ANFAccounts[0].Pools[0].Volumes
 
    # Get number of volumes of the $ANFAccounts[0] first pool
    $ANFAccounts[0].Pools[0].Volumes.Count
 
    # Get first volume of the first pool of the account $ANFAccounts[0]
    $ANFAccounts[0].Pools[0].Volumes[0]
 
    # Get second volume of the first pool of the account $ANFAccounts[0]
    $ANFAccounts[0].Pools[0].Volumes[1]
 
    # get IP adresss of the volume
    ($ANFAccounts | where Name -eq gor-anf-we).Pools[1].Volumes[2].MountTargets.IpAddress
 
    #Get T2 spine info
    ($ANFAccounts | where Name -eq gor-anf-we).Pools[1].Volumes[2].T2Network
 
    # Get NFS address
    ($ANFAccounts | where Name -eq gor-anf-we).Pools[1].Volumes[2].NFSAddress
 
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.NetAppFiles
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                
                Write-Verbose "Executing Get-AzNetAppFilesAccountsPoolsVolumes ..."

                # Get ALL ANF accounts in a Subscription
                $AllAzureANFResourceAccounts = Get-AzResource -ResourceType "Microsoft.NetApp/netAppAccounts"

                foreach ($ANFAccount in $AllAzureANFResourceAccounts) {

                    $ANFObject = New-Object -TypeName psobject  
                    $ANFObject = $ANFAccount
                    
                    $ANFAccountResourceGroupName = $ANFAccount.ResourceGroupName
                    $ANFAccountName = $ANFAccount.Name

                    # get all ANF Pools in ANF Account
                    $ANFPools = Get-AzNetAppFilesPool -ResourceGroupName $ANFAccount.ResourceGroupName -AccountName $ANFAccountName
                    $ANFPoolsExtendedObjects = @()

                    foreach ($ANFPool in $ANFPools) {
                        $ANFPoolName = $ANFPool.Name.Replace($ANFAccountName+"/","")
                                            
                        $ANFVolumesInPool = Get-AzNetAppFilesVolume -ResourceGroupName $ANFAccountResourceGroupName -AccountName $ANFAccountName -PoolName $ANFPoolName
                        
                        # Get all volumes in a ANF pool
                        $ANFVolumesExtendedObjects = @()
                        foreach ($ANFVolumeInPool in $ANFVolumesInPool) {

                            #get the volume name from 'gor-anf-we/UltraPool2/SO2-data-mnt00001'
                            $VolumeNameShort = $ANFVolumeInPool.Name.Split("/")[2]

                            # we can have more than one MountTarget e.g. IPAddres
                            $NFSAddresses = @()
                            foreach ($MountTarget in $ANFVolumeInPool.MountTargets) {
                                $NFSAddress      = $MountTarget.IpAddress +  ":/"  + $VolumeNameShort                            
                                $NFSAddresses   += $NFSAddress
                            }

                            
                            #$NFSAddress = $ANFVolumeInPool.MountTargets.IpAddress + ":/" + $VolumeNameShort

                            # Define new extended volume object
                            $ANFVolumeExtendedObject =New-Object -TypeName psobject
                            $ANFVolumeExtendedObject = $ANFVolumeInPool

                            # Extend volume abject with NFSAddress
                            $ANFVolumeExtendedObject | Add-Member -NotePropertyName "NFSAddresses" -NotePropertyValue $NFSAddresses

                            $ANFVolumesExtendedObjects += $ANFVolumeExtendedObject 
                        }

                        # Define new extended Pool object
                        $ANFPoolExtendedObject =New-Object -TypeName psobject
                        $ANFPoolExtendedObject = $ANFPool
                        # Extend Pool abject with all volumes
                        #$ANFPoolExtendedObject | Add-Member -NotePropertyName "Volumes" -NotePropertyValue $ANFVolumesInPool
                        $ANFPoolExtendedObject | Add-Member -NotePropertyName "Volumes" -NotePropertyValue $ANFVolumesExtendedObjects

                        $ANFPoolsExtendedObjects += $ANFPoolExtendedObject 
                    }

                    $ANFObject | Add-Member -NotePropertyName "Pools" -NotePropertyValue $ANFPoolsExtendedObjects
                    
                    Write-Output $ANFObject                    
                }

                Write-Verbose "Get-AzNetAppFilesAccountsPoolsVolumes executed succesfully."
            }
            catch{                 
                Write-Error "Error while executing Get-AzNetAppFilesAccountsPoolsVolumes. $_.Exception.Message"         
            }
    
        }
    
        END {}
}

function Get-AzVMNetworkInterfaceCards {
    <#
    .SYNOPSIS
    Get-AzVMNetworkInterfaceCards gest virtual machine all network cards
     
    .DESCRIPTION
     Get-AzVMNetworkInterfaceCards gest virtual machine all network cards
     
    .PARAMETER VMResourceGroupName
    VM Resource Group Name.
     
    .PARAMETER VirtualMachineName
    Virtual Machine Name.
     
     
    .EXAMPLE
    $NICs = Get-AzVMNetworkInterfaceCards -VMResourceGroupName gor-vmssflex1 -VirtualMachineName test1
 
    # Get NIC 0 Id
    $NICs[0].Id
 
    # Get NIC 1 Id
    $NICs[1].Id
     
 #>
 
    
    [CmdletBinding()]
    param(       
        [Parameter(Mandatory)]    
        $VM                                          
            
    )
    
    BEGIN {}
        
    PROCESS {
        try {                  
            write-verbose "Executing Get-AzVMNetworkInterfaceCards ..."                              

            foreach ($nic in $VM.NetworkProfile.NetworkInterfaces) {
                $NetworkCardObject = Get-AzNetworkInterface -ResourceId $nic.Id                         
                Write-Output $NetworkCardObject
            }            

            write-verbose "Succesfully executed Get-AzVMNetworkInterfaceCards."                              
        }
        catch {
            Write-Error  $_.Exception.Message
        }
    
    }
    
    END {}
}

function Get-AzAzureFileShareAndStorageAccount {
    <#
    .SYNOPSIS
    Get-AzAzureFileShareAndStorageAccount looks for Azure storage account and file share in ALL subscription Azure storage accounts.
 
    .DESCRIPTION
    Get-AzAzureFileShareAndStorageAccount looks for Azure storage account and file share in ALL subscription Azure storage accounts.
    When it finds, it returns object that consist of 'AzureStorageAccount' and 'AzureFileShare' information.
         
    .EXAMPLE
    $OneAcc = Get-AzAzureFileShareAndStorageAccount -AzureStorageAccountName "s41sapafsnfs2" -AzurFileShareName "sapmnt"
 
    # Get detail info on Azure Storage Account
    $OneAcc.AzureStorageAccount
 
    # Get detail info on Azure file share
    $OneAcc.AzureFileShare
 
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    [CmdletBinding()]
    param(
     
        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]               
        [string] $AzureStorageAccountName,

        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]               
        [string] $AzurFileShareName,
        
        [Parameter(Mandatory=$false,ValueFromPipeline=$True)]               
        [object] $AzureStorageAccounts        
    )
    
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                
                Write-Verbose "Executing Get-AzAzureFileShareAndStorageAccount ..."

                if(-not $AzureStorageAccounts){
                    # Get all Azure Storage Accounts
                    Write-Verbose "Collecting all Azure Storage Accounts in the subscription ..."
                    $AzureStorageAccounts = Get-AzStorageAccount
                }
                
                # search for $AzureStorageAccountName inside of all Azure Storage Accounts
                $OneAzureStorageAccount = $AzureStorageAccounts | where StorageAccountName -Like $AzureStorageAccountName

                # Remove 'Context' property - 'Context' is referencing itself endless, and therefore braking JSON structure limit of 100 depth items when using ConvertTo-Json cmdlet
                $OneAzureStorageAccount = $OneAzureStorageAccount | Select-Object -Property * -ExcludeProperty Context

                if ($OneAzureStorageAccount) {
                    # We found a match
                    Write-Verbose "Found Azure storage account '$AzureStorageAccountName'."

                    $AzureFileShareAndStorageAccontExtendedObject = New-Object -TypeName psobject  

                    $AzureFileShareAndStorageAccontExtendedObject | Add-Member -NotePropertyName "AzureStorageAccount"  -NotePropertyValue $OneAzureStorageAccount                    

                    # get all shares belonging to Azure Storage Account
                    $Shares = Get-AzRmStorageShare -ResourceGroupName $OneAzureStorageAccount.ResourceGroupName -StorageAccountName  $OneAzureStorageAccount.StorageAccountName
                    
                    $OneShare = $Shares | where Name -EQ $AzurFileShareName
                    if ($OneShare) {
                        # found share
                        Write-Verbose "Found file share '$AzurFileShareName' belonging to Azure storage account '$AzureStorageAccountName'."

                        $AzureFileShareAndStorageAccontExtendedObject | Add-Member -NotePropertyName "AzureFileShare"  -NotePropertyValue $OneShare
                    }else {
                        Write-Verbose "Did not find file share '$AzurFileShareName' belonging to Azure storage account '$AzureStorageAccountName'."
                    }

                    Write-Output $AzureFileShareAndStorageAccontExtendedObject                    
                }else {
                    Write-Verbose "Did not find Azure storage account '$AzureStorageAccountName'."
                }
                
                Write-Verbose "Get-AzAzureFileShareAndStorageAccount executed succesfully."
            }
            catch{                 
                Write-Error "Error while executing Get-AzAzureFileShareAndStorageAccount. $_.Exception.Message"         
            }
    
        }
    
        END {}
}

function Test-AzVMSKUEphemeralOSDiskSupport {
    <#
    .SYNOPSIS
    Test-AzVMSKUEphemeralOSDiskSupport checks if Azure VM SKU ina region supports ephemeral OS disk.
 
    .DESCRIPTION
    Test-AzVMSKUEphemeralOSDiskSupport retruns 'True' string is VM SKU in a region has Ephemeral OS disk, or 'False' string if it has not. In case that SM SKU do not exists ina region (and in any zone) exception with error message is writteb, and no result is returned.
         
    .EXAMPLE
    # returns 'True' - VM SKU Standard_M416-208ms_v2 exists in West Europe and have ephemeral OS disk.
 
    Test-AzVMSKUEphemeralOSDiskSupport -Location "westeurope" -VMSKU "Standard_M416-208ms_v2"
 
    .EXAMPLE
    # returns 'False' - VM SKU Standard_E8as_v5 exists in West Europe and do not have ephemeral OS disk.
 
    Test-AzVMSKUEphemeralOSDiskSupport -Location "westeurope" -VMSKU "Standard_E8as_v5"
 
    .EXAMPLE
    # ERROR message - nothing is returned - Standard_M176s_4_v3 do not exists in region Norway East
 
    Test-AzVMSKUEphemeralOSDiskSupport -Location "NorwayEast" -VMSKU "Standard_M176s_4_v3"
 
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    [CmdletBinding()]
    param(
     
        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]               
        [string] $Location,

        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]               
        [string] $VMSKU
    )
    
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                
                Write-Verbose "Executing Test-AzVMSKUEphemeralOSDiskSupport ..."

                $VmSKUsinRegion = Get-AzComputeResourceSku $Location

                $SupportedRegionalVMSKUZones = Get-AzComputeSKUZonesAvailability -Location $Location -VMSKU $VMSKU

                $ExtendedVMSKUObject = $VmSKUsinRegion | where  { ($_.Name -eq $VMSKU) }

                # return 'True' or 'False' as a string
                $EphemeralOSDiskSupported = ($ExtendedVMSKUObject.Capabilities | where name -eq EphemeralOSDiskSupported).Value

                Write-Output $EphemeralOSDiskSupported

                
                Write-Verbose "Test-AzVMSKUEphemeralOSDiskSupport executed succesfully."
            }
            catch{                 
                Write-Error "Error while executing Test-AzVMSKUEphemeralOSDiskSupport. $_.Exception.Message"         
            }
    
        }
    
        END {}
}

function Test-AzVMSKUTemporaryDiskSupport {
    <#
    .SYNOPSIS
    Test-AzVMSKUTemporaryDiskSupport checks if Azure VM SKU ina region supports temporary disk.
 
    .DESCRIPTION
    Test-AzVMSKUTemporaryDiskSupport checks if Azure VM SKU in a region supports temporary disk. It retruns 'True' or 'False'. In case that SM SKU do not exists ina region (and in any zone) exception with error message is written, and no result is returned.
         
    .EXAMPLE
    # returns 'True' - VM SKU Standard_M416-208ms_v2 exists in West Europe and have temp disk.
    Test-AzVMSKUTemporaryDiskSupport -Location "westeurope" -VMSKU "Standard_M416-208ms_v2"
 
    .EXAMPLE
    # returns 'False' - VM SKU Standard_E8as_v5 exists in West Europe and do not have temp disk.
    Test-AzVMSKUTemporaryDiskSupport -Location "westeurope" -VMSKU "Standard_E8as_v5"
 
    .EXAMPLE
    # ERROR message - nothing is returned - Standard_M176s_4_v3 do not exists in region Norway East
    Test-AzVMSKUTemporaryDiskSupport -Location "NorwayEast" -VMSKU "Standard_M176s_4_v3"
 
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    [CmdletBinding()]
    param(
     
        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]               
        [string] $Location,

        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]               
        [string] $VMSKU
    )
    
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                
                Write-Verbose "Executing Test-AzVMSKUTemporaryDiskSupport ..."

                # If VM SKU has Ephemeral OS, then it has temp disk, otherwise not.
                $AzVMSKUEphemeralOSDiskSupport = Test-AzVMSKUEphemeralOSDiskSupport -Location $Location -VMSKU $VMSKU

                Write-Output $AzVMSKUEphemeralOSDiskSupport
                
                Write-Verbose "Test-AzVMSKUTemporaryDiskSupport executed succesfully."
            }
            catch{                 
                Write-Error "Error while executing Test-AzVMSKUTemporaryDiskSupport. $_.Exception.Message"         
            }
    
        }
    
        END {}
}

function Get-AzVMFamilyFromSKU {
    <#
    .SYNOPSIS
    Get-AzVMFamilyFromSKU.
 
    .DESCRIPTION
    Get-AzVMFamilyFromSKU.
         
    .EXAMPLE
     
    # Msv2 Medium Memory Diskless
    # https://learn.microsoft.com/en-us/azure/virtual-machines/msv2-mdsv2-series#msv2-medium-memory-diskless
 
    Get-AzVMFamilyFromSKU -VMSKU "Standard_M32s_v2"
    Get-AzVMFamilyFromSKU -VMSKU "Standard_M32ms_v2"
    Get-AzVMFamilyFromSKU -VMSKU "Standard_M192is_v2"
    Get-AzVMFamilyFromSKU -VMSKU "Standard_M192ims_v2"
 
    # Output is 'StandardMSv2Family'
 
    .EXAMPLE
    # Mdsv2 Medium Memory with Disk
    # https://learn.microsoft.com/en-us/azure/virtual-machines/msv2-mdsv2-series#mdsv2-medium-memory-with-disk
 
    Get-AzVMFamilyFromSKU -VMSKU "Standard_M32dms_v2"
    Get-AzVMFamilyFromSKU -VMSKU "Standard_M64ds_v2"
    Get-AzVMFamilyFromSKU -VMSKU "Standard_M128ds_v2"
    Get-AzVMFamilyFromSKU -VMSKU "Standard_M128dms_v2"
    Get-AzVMFamilyFromSKU -VMSKU "Standard_M192ids_v2"
    Get-AzVMFamilyFromSKU -VMSKU "Standard_M192idms_v2"
 
    # Output is 'standardMDSMediumMemoryv2Family'
     
    .EXAMPLE
     
    .LINK
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    [CmdletBinding()]
    param(
     
        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]               
        [string] $VMSKU
    )
    
    
        BEGIN{        
                
        }
        
        PROCESS{
            try{   
                
                Write-Verbose "Executing Get-AzVMFamilyFromSKU ..."

                switch -regex ($VMSKU) {

                    # Msv2 Medium Memory Diskless
                    # https://learn.microsoft.com/en-us/azure/virtual-machines/msv2-mdsv2-series#msv2-medium-memory-diskless

                    # OR
                    
                    # Mv2-series
                    # https://learn.microsoft.com/en-us/azure/virtual-machines/mv2-series
                    '^Standard_M[0-9]+[i]?[m]?s_v2$'  
                        { 
                            # ^ = BEGNINING
                            # Standard_M = 'Standard_M'
                            # [0-9]+ = 1 or more times numbers
                            # [m]? = 0 or 1 time 'm'
                            # [i]? = 0 or 1 time 'i'
                            # s_v2 = 's_v2'
                            # $ - END

                            if (($VMSKU -match "M208") -or ($VMSKU -match "M416")) {
                                Write-Output "StandardMv2Family???" 
                            }else {
                                Write-Output "StandardMSv2Family"  
                            }
                            
                        }
                    

                    # Mdsv2 Medium Memory with Disk
                    # https://learn.microsoft.com/en-us/azure/virtual-machines/msv2-mdsv2-series#mdsv2-medium-memory-with-disk
                    '^Standard_M[0-9]+[i]?d[m]?s_v2$'  
                    { 
                        # ^ = BEGNINING
                        # Standard_M = 'Standard_M'
                        # [0-9]+ = 1 or more times numbers
                        # [i]? = 0 or 1 time 'i'
                        # d = 'd'
                        # [m]? = 0 or 1 time 'm'
                        # s_v2 = 's_v2'
                        # $ - END
                        Write-Output "standardMDSMediumMemoryv2Family"  
                    }
                    
                    Default { Write-Host "NO_MATCH" }
                }
                
                
                Write-Verbose "Get-AzVMFamilyFromSKU executed succesfully."
            }
            catch{                 
                Write-Error "Error while executing Get-AzVMFamilyFromSKU. $_.Exception.Message"         
            }
    
        }
    
        END {}
}
  
function Get-AzVMUniqueueExportImportJSONFileNamePath {
    <#
    .SYNOPSIS
        
    .DESCRIPTION
    Get-AzVMUniqueueExportImportJSONFileNamePath return an object with two uniqueue full path:
      - <CurrentScriptFolder>/<VMName>_<ResourceGroupName>_<SubscriptionId>-orig.json
      - <CurrentScriptFolder>/<VMName>_<ResourceGroupName>_<SubscriptionId>-new.json
 
    The first one is sed to export ARM template. Second one is used in Resize-AzVM to resize to new VM SKU.
         
    .EXAMPLE
    $ret = Get-AzVMUniqueueExportImportJSONFileNamePath -ResourceGroupName rg1-eastus -VirtualMachineName vm1
 
    # Original VM file name
    $ret.OriginalARMVMFullPathFile
     
    # output like:
    C:\Users\user1\vm1_rg1-eastus_e6789c2d-722b-4be1-b636-bbd9e4c6h786-orig.json
 
    # New VM file path
    $ret.NewARMVMFullPathFile
     
    # output like:
    C:\Users\user1\vm1_rg1-eastus_e6789c2d-722b-4be1-b636-bbd9e4c6h786-new.json
 
 
    .LINK
         
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
            
    [CmdletBinding()]
    param(
                        
        [Parameter(Mandatory=$True)]                    
        [ValidateNotNullOrEmpty()]        
        [string] $ResourceGroupName,
                                  
        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VirtualMachineName   
    )
    
    BEGIN{        
            
    }
    
    PROCESS{
        try{   
            Write-Verbose "Executing Get-AzVMUniqueueExportImportJSONFileNamePath ..."


            $CurrentDirectory = (Get-Location).Path
            Write-Verbose "Script directory is '$CurrentDirectory'."

            $AzureSubscriptionId = (Get-AzSubscriptionContext).Id       
            Write-Verbose "Azure subscription Id '$AzureSubscriptionId'."     

            Write-Verbose "VM name is '$VirtualMachineName'."    
            Write-Verbose "VM resource group name is '$ResourceGroupName'."    
                                                
            $JSONPathOrig               = $CurrentDirectory + "/" + $VirtualMachineName + "_" + $ResourceGroupName + "_" + $AzureSubscriptionId + "-orig-ARM.json"
            $JSONPathNew                = $CurrentDirectory + "/" + $VirtualMachineName + "_" + $ResourceGroupName + "_" + $AzureSubscriptionId + "-new-ARM.json"
            $PowerShellJSONOrig         = $CurrentDirectory + "/" + $VirtualMachineName + "_" + $ResourceGroupName + "_" + $AzureSubscriptionId + "-orig-PowerShell.json"
            $VMOSDiskRestore            = $CurrentDirectory + "/" + $VirtualMachineName + "_" + $ResourceGroupName + "_" + $AzureSubscriptionId + "-Restore.json"
            $JSONRestoreARMPathOrig     = $CurrentDirectory + "/" + $VirtualMachineName + "_" + $ResourceGroupName + "_" + $AzureSubscriptionId + "-Restore-ARM.json"

            Write-Verbose "Generated original VM JSON file path is '$JSONPathOrig'."     
            Write-Verbose "Generated new VM JSON file path is '$JSONPathNew'."   
            Write-Verbose "Generated new VM PowerShell file path is '$PowerShellJSONOrig'."     

            $FilesPathObject = New-Object -Type PSObject
            $FilesPathObject | Add-Member -NotePropertyName "OriginalARMVMFullPathFile"             -NotePropertyValue $JSONPathOrig
            $FilesPathObject | Add-Member -NotePropertyName "NewARMVMFullPathFile"                  -NotePropertyValue $JSONPathNew
            $FilesPathObject | Add-Member -NotePropertyName "OriginalPowerShellVMFullPathFile"      -NotePropertyValue $PowerShellJSONOrig
            $FilesPathObject | Add-Member -NotePropertyName "VMOSDiskRestore"                       -NotePropertyValue $VMOSDiskRestore
            $FilesPathObject | Add-Member -NotePropertyName "OriginalRestoreARMVMFullPathFile"      -NotePropertyValue $JSONRestoreARMPathOrig

            Write-Output $FilesPathObject
            
            Write-Verbose "Get-AzVMUniqueueExportImportJSONFileNamePath executed succesfully."
        }
        catch{            
            Write-Error "Error while executing Get-AzVMUniqueueExportImportJSONFileNamePath. $_.Exception.Message"         
        }

    }

    END {}
}
function Get-AzVMScriptLogFileFullPath {
    <#
    .SYNOPSIS
        
    .DESCRIPTION
     
         
    .EXAMPLE
    
 
 
    .LINK
         
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
            
    [CmdletBinding()]
    param(
                        
        [Parameter(Mandatory=$True)]                    
        [ValidateNotNullOrEmpty()]        
        [string] $ResourceGroupName,
                                  
        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VirtualMachineName,

        [Parameter(Mandatory=$False)]                    
        [ValidateNotNullOrEmpty()]        
        [string] $LogDirectory
        
    )
    
    BEGIN{        
            
    }
    
    PROCESS{
        try{   
            Write-Verbose "Executing Get-AzVMScriptLogFileFullPath ..."

            if (-not $LogDirectory) {
                $LogDirectory = (Get-Location).Path             
            }

            Write-Verbose "Script directory is '$LogDirectory'."
            
            $AzureSubscriptionId = (Get-AzSubscriptionContext).Id       
            Write-Verbose "Azure subscription Id '$AzureSubscriptionId'."     

            Write-Verbose "VM name is '$VirtualMachineName'."    
            Write-Verbose "VM resource group name is '$ResourceGroupName'."    
                                                
            $CurrentTime = Get-Date            
            $LogFileName   = $LogDirectory + "/" + $VirtualMachineName + "_" + $ResourceGroupName + "_" + $AzureSubscriptionId + "_" + $CurrentTime.Year + "_" + $CurrentTime.Month + "_" + $CurrentTime.Day + "_" + $CurrentTime.Hour + "_" + $CurrentTime.Minute + ".log"

            Write-Verbose "Full path of the log file is '$LogFileName'."

            Write-Output $LogFileName        
            
            Write-Verbose "Get-AzVMScriptLogFileFullPath executed succesfully."
        }
        catch{            
            Write-Error "Error while executing Get-AzVMScriptLogFileFullPath. $_.Exception.Message"         
        }

    }

    END {}
}

function Get-AzVMOSDiskInfo {
    <#
    .SYNOPSIS
        
    .DESCRIPTION
         
    .EXAMPLE
     
 
    .LINK
         
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
            
    [CmdletBinding()]
    param(
                        
        [Parameter(Mandatory=$True)]                    
        [ValidateNotNullOrEmpty()]        
        $VM
    )
    
    BEGIN{        
            
    }
    
    PROCESS{
        #try{
        Write-Verbose "Executing Get-AzVMOSDiskInfo ..."

        # Get the OS Disk info
        [string] $Location      = $VM.Location
        [string] $StorageType   = $VM.StorageProfile.OsDisk.ManagedDisk.StorageAccountType
        [string] $OSDiskName    = $VM.StorageProfile.OsDisk.Name                                                    
        $OSDisk                 = Get-AzDisk -ResourceGroupName $VM.ResourceGroupName  -DiskName  $OSDiskName -ErrorAction Stop
        $OSDiskTags             = $OSDisk.Tags                     
        # when non-Zonal disk / VM this value is an empty string
        [string] $VMDiskZone    = $VM.Zones   

        $VMOSDiskObject = New-Object -Type PSObject
        $VMOSDiskObject | Add-Member -NotePropertyName "OSDisk"         -NotePropertyValue $OSDisk
        $VMOSDiskObject | Add-Member -NotePropertyName "Location"       -NotePropertyValue $Location
        $VMOSDiskObject | Add-Member -NotePropertyName "StorageType"    -NotePropertyValue $StorageType
        $VMOSDiskObject | Add-Member -NotePropertyName "OSDiskName"     -NotePropertyValue $OSDiskName            
        $VMOSDiskObject | Add-Member -NotePropertyName "OSDiskTags"     -NotePropertyValue $OSDiskTags
        $VMOSDiskObject | Add-Member -NotePropertyName "OSDiskZone"     -NotePropertyValue $VMDiskZone

        Write-Output $VMOSDiskObject
                        
        Write-Verbose "Get-AzVMOSDiskInfo executed succesfully."
        #}
        #catch{
        #Write-Error "Error while executing Get-AzVMOSDiskInfo. $_.Exception.Message"
        #}

    }

    END {}
}

function Update-AzVMARMTemplateForReDeploy {
    <#
    .SYNOPSIS
        
    .DESCRIPTION
     
         
    .EXAMPLE
     
    .LINK
         
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
            
    [CmdletBinding()]
    param(
                                
        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        [string] $SourceVMFullPathFile,

        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        $NewVMSKU
    )
    
    BEGIN{        
            
    }
    
    PROCESS{
        try{   
            Write-Verbose "Executing Update-AzVMARMTemplateForReDeploy ..."

            Write-WithTime_Using_WriteHost  "Updating Azure VM ARM teplate ..."                                    
            $VMImportedFromJSONObject = Get-Content $SourceVMFullPathFile -ErrorAction Stop | ConvertFrom-Json

            # Remove 'imageReference'
            $VMImportedFromJSONObject.resources[0].properties.storageProfile.PSObject.Properties.Remove('imageReference')

            # Remove osProfile
            $VMImportedFromJSONObject.resources[0].properties.PSObject.Properties.Remove('osProfile')

            # Set OS Disk 'createOption' to Attach
            $VMImportedFromJSONObject.resources[0].properties.storageProfile.osDisk.createOption='Attach'

            # Set Data Disks 'createOption' to Attach
            [int] $i = 0
            foreach ($DataDisk in $VMImportedFromJSONObject.resources[0].properties.storageProfile.dataDisks) {
                $VMImportedFromJSONObject.resources[0].properties.storageProfile.dataDisks[$i].createOption = 'Attach'
                $i += 1
            }

            # Update new VM SKU
            $VMImportedFromJSONObject.resources[0].properties.hardwareProfile.vmSize=$NewVMSKU

            Write-Output $VMImportedFromJSONObject

            Write-Verbose "Update-AzVMARMTemplateForReDeploy executed succesfully."
        }
        catch{            
            Write-Error "Error while executing Update-AzVMARMTemplateForReDeploy. $_.Exception.Message"         
        }

    }

    END {}
}

function Test-AzVMSKUPremiumDiskSupport {
    <#
    .SYNOPSIS
        
    .DESCRIPTION
     
         
    .EXAMPLE
    Test-AzVMSKUPremiumDiskSupport -Location norwayeast -VMSKU Standard_E8as_v5
 
    .LINK
    .EXAMPLE
    Test-AzVMSKUPremiumDiskSupport -Location norwayeast -VMSKU Standard_E16_v5
         
     
    .NOTES
        v0.1 - Initial version
     
    #>

    
    #Requires -Modules Az.Compute
    #Requires -Version 5.1
            
    [CmdletBinding()]
    param(
                                
        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        [string] $Location,

        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        $VMSKU
    )
    
    BEGIN{        
            
    }
    
    PROCESS{
        try{   
            Write-Verbose "Executing Test-AzVMSKUPremiumDiskSupport ..."

            Write-Verbose "Getting Azure VM SKUs in region '$Location' ..."
            $resourceSkus = Get-AzComputeResourceSku | Where-Object { $_.Locations -contains $Location -and $_.ResourceType -eq 'virtualMachines' }

            # Check if the VM size supports premium storage
            Write-Verbose "Getting VM SKU '$VMSKU' in region '$Location' information ..."
            $VMSKUInRegion = $resourceSkus | Where-Object { $_.Name -eq $VMSKU }
            if ($VMSKUInRegion) {
                Write-Verbose "Found VM SKU '$VMSKU' in region '$Location'."
            }else {
                Write-Verbose "VM SKU '$VMSKU' in region '$Location' do not exists! Please check your VM SKU."
                Throw "VM SKU '$VMSKU' in region '$Location' do not exists! Please check your VM SKU."
            }

            Write-Verbose "Checking if VM SKU '$VMSKU' in region '$Location' supports Premium disks ..."
            $premiumStorageSupported = $VMSKUInRegion.Capabilities | Where-Object { $_.Name -eq 'PremiumIO' -and $_.Value -eq 'True' }

            if ($premiumStorageSupported.Value -EQ "True") {
                Write-Output $true
            } else {
                Write-Output $false
            }

            Write-Verbose "Test-AzVMSKUPremiumDiskSupport executed succesfully."
        }
        catch{            
            Write-Error "Error while executing Test-AzVMSKUPremiumDiskSupport. $_.Exception.Message"         
        }

    }

    END {}
}

function Test-AzResourceGroupExist {
    <#
    .SYNOPSIS
     
     
    .DESCRIPTION
     
    .EXAMPLE
     
 #>
 
    
    [CmdletBinding()]
    param(                    

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $ResourceGroupName
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                    
            $RG = Get-AzResourceGroup -Name $ResourceGroupName

            if ($RG) {
                return $True
            }  else {
                return $false
            }            
        }
        catch {
            Write-Error  $_.Exception.Message
            #Write-WithTime_Using_WriteHost $_.Exception.Message -Level "ERROR"
        }    
    }
    
    END {}
}

function Test-AzVirtualMachineExist {
    <#
    .SYNOPSIS
     
     
    .DESCRIPTION
     
    .EXAMPLE
     
 #>
 
    
    [CmdletBinding()]
    param(                    

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $ResourceGroupName,

        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VirtualMachineName
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                    
            Write-Verbose "Executing Test-AzVirtualMachineExist ..."

            $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -ErrorAction Stop

            if ($VM) {
                return $True
            }  else {
                return $false
            }           
            
            Write-Verbose "Test-AzVirtualMachineExist executed succesfully."
        }
        catch {
            Write-Error "Error while executing Test-AzVirtualMachineExist. $_.Exception.Message"  
            Throw  $_.Exception.Message       
        }    
    }
    
    END {}
}
function Test-AzDiskExist {
    <#
    .SYNOPSIS
     
     
    .DESCRIPTION
     
    .EXAMPLE
     
 #>
 
    
    [CmdletBinding()]
    param(                    

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $ResourceGroupName,

        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        [string] $DiskName
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                    
            Write-Verbose "Executing Test-AzDiskExist ..."
            Write-Verbose "Checking existance of the disk '$DiskName' in resource group '$ResourceGroupName' ..."

            $CheckDisk = Get-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $DiskName -ErrorVariable -notPresent  -ErrorAction SilentlyContinue

            if ($null -ne $CheckDisk ) {               
                Write-Verbose  "Disk '$DiskName' in resource group '$ResourceGroupName' alredy exist."
                return $True                
            }else {                
                Write-Verbose  "Disk '$DiskName' in resource group '$ResourceGroupName' do not exist."      
                return $false          
            }            
            
            Write-Verbose "Test-AzDiskExist executed succesfully."
        }
        catch {
            Write-Error "Error while executing Test-AzDiskExist. $_.Exception.Message"  
            Throw  $_.Exception.Message       
        }    
    }
    
    END {}
}

function Get-AzVMPPG {
    <#
    .SYNOPSIS
     
     
    .DESCRIPTION
     
    .EXAMPLE
     
 #>
 
    
    [CmdletBinding()]
    param(                    

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $ResourceGroupName,

        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VirtualMachineName
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                    
            Write-Verbose "Executing Get-AzVMPPG ..."
            $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -ErrorAction Stop

            if ($VM.ProximityPlacementGroup.Id) {
                Write-Verbose "Virtual machnine '$VirtualMachineName' in resource group '$ResourceGroupName' has configured proximity placemnt group with Id '$($VM.ProximityPlacementGroup.Id)'."
            }else {
                Write-Verbose "Virtual machnine '$VirtualMachineName' in resource group '$ResourceGroupName' has no configured proximity placemnt group."
            }

            Write-Output $VM.ProximityPlacementGroup.Id   

            Write-Verbose "Get-AzVMPPG executed succesfully."
        }
        catch {            
            Write-Error "Error while executing Get-AzVMPPG. $_.Exception.Message"    
            Throw $_.Exception.Message           
        }    
    }
    
    END {}
}

function Test-AzVMPPG {
    <#
    .SYNOPSIS
     
     
    .DESCRIPTION
     
    .EXAMPLE
     
 #>
 
    
    [CmdletBinding()]
    param(                    

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]        
        [string] $ResourceGroupName,

        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VirtualMachineName
    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                    
            Write-Verbose "Executing Get-AzVMPPG ..."
            $VMPPG = Get-AzVMPPG -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName

            if ($VMPPG) {
                return $true
            }else {
                return $false
            }            

            Write-Verbose "Get-AzVMPPG executed succesfully."
        }
        catch {            
            Write-Error "Error while executing Get-AzVMPPG. $_.Exception.Message"                
        }    
    }
    
    END {}
}

function Get-AzVMRestoreInformation {
    <#
    .SYNOPSIS
     
     
    .DESCRIPTION
     
    .EXAMPLE
     
 #>
 
    
    [CmdletBinding()]
    param(                            

        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        [object] $VM,

        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]        
        [string] $OSDiskSnapshotId,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]        
        [object] $OSDiskInfo

    )
    
    BEGIN {}
        
    PROCESS {
        try {   
                    
            Write-Verbose "Executing Get-AzVMRestoreInformation ..."                      

            $VMObject = [PSCustomObject]@{
                        Name                    = $VM.Name
                        ResourceGroupName       =  $VM.ResourceGroupName
                        Id                      = $VM.Id
                        HardwareProfile         = [PSCustomObject]@{
                            VmSize              = $VM.HardwareProfile.VmSize
                            VmSizeProperties    = $VM.HardwareProfile.VmSizeProperties
                        }        
                        StorageProfile = [PSCustomObject]@{
                            OsDisk = [PSCustomObject]@{
                                Name                = $VM.StorageProfile.OsDisk.Name
                                OsType              = $VM.StorageProfile.OsDisk.OsType
                                OSDiskSnapshotId    = $OSDiskSnapshotId
                            }                        
                        }
                        Zones       = $VM.Zones                        
                        Extensions  = $VM.Extensions
                        OSDiskInfo  = $OSDiskInfo
            }

            Write-Output $VMObject

            Write-Verbose "Get-AzVMRestoreInformation executed succesfully."
        }
        catch {            
            Write-Error "Error while executing Get-AzVMRestoreInformation. $_.Exception.Message"                
        }    
    }
    
    END {}
}




Function Get-AzVMExtensions {
    <#
        .SYNOPSIS
        $VM.Extensions
     
        .DESCRIPTION
        $VM.Extensions
         
        .PARAMETER VMResourceGroupName
        Virtual Machine Resource Group Name
                 
        .PARAMETER VirtualMachineName
        Virtual Machine Name
 
             
        .EXAMPLE
         
        .LINK
         
        .NOTES
            v0.1 - Initial version
         
    #>

        
        #Requires -Modules Az.Compute
        #Requires -Version 5.1
        
            
                [CmdletBinding()]
                param(
                                    
                    [Parameter(Mandatory=$True)]                    
                    [ValidateNotNullOrEmpty()]        
                    [string] $ResourceGroupName,
                                              
                    [Parameter(Mandatory=$True)]
                    [ValidateNotNullOrEmpty()]        
                    [string] $VirtualMachineName   
                )
            
                BEGIN{
                }
                
                PROCESS{
                    try{                
                                                                        
                                                
                        $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName 

                        Write-Output $VM.Extensions
                        
                                                                            
                    }
                    catch{
                       Write-Error  $_.Exception.Message                          
                   }
                   
                   finally {
                    
                   }
                }
            
                END {}
}

Function Write-AzVMExtensions {
    <#
        .SYNOPSIS
         
     
        .DESCRIPTION
         
         
        .PARAMETER VMExtensions
         
                 
         
 
             
             
        .EXAMPLE
         
         
        .LINK
         
        .NOTES
            v0.1 - Initial version
         
    #>

        
        #Requires -Modules Az.Compute
        #Requires -Version 5.1
        
            
                [CmdletBinding()]
                param(
                                    
                    [Parameter(Mandatory=$True)]                    
                    [ValidateNotNullOrEmpty()]        
                    [Object] $VMExtensions
                )
            
                BEGIN{
                }
                
                PROCESS{
                    try{                
                                                                        
                    
                        [int] $i = 1  
                        foreach ($Extension in $VMExtensions) {
                            Write-WithTime_Using_WriteHost "$i. virtual machine extension type '$($Extension.VirtualMachineExtensionType)' , Publisher '$($Extension.Publisher)', Type handler version '$($Extension.TypeHandlerVersion)'"
                            $i++
                        }
                        
                                                                            
                    }
                    catch{
                       Write-Error  $_.Exception.Message                          
                   }
                   
                   finally {
                    
                   }
                }
            
                END {}
}

Function Restore-AzVMExtensions {
    <#
        .SYNOPSIS
         
     
        .DESCRIPTION
         
         
        .PARAMETER VMResourceGroupName
        Virtual Machine Resource Group Name
                 
        .PARAMETER VirtualMachineName
        Virtual Machine Name
 
        .PARAMETER VMExtensions
        Object with VM extensions
 
             
        .EXAMPLE
         
        .LINK
         
        .NOTES
            v0.1 - Initial version
         
    #>

        
        #Requires -Modules Az.Compute
        #Requires -Version 5.1
        
            
                [CmdletBinding()]
                param(
                                    
                    [Parameter(Mandatory=$True)]                    
                    [ValidateNotNullOrEmpty()]        
                    [string] $ResourceGroupName,
                                              
                    [Parameter(Mandatory=$True)]
                    [ValidateNotNullOrEmpty()]        
                    [string] $VirtualMachineName,                                                                                                                   

                    [Parameter(Mandatory=$True)]
                    [object] $VMExtensions         
                )
            
                BEGIN{
                }
                
                PROCESS{
                    try{                
                        foreach ($Extension in $VMExtensions) {
                            $VMExtensionIsSupportedForRestore = $SupportedAzureVMExtensions | where Name -EQ $Extension.Name
                            if ($VMExtensionIsSupportedForRestore) {
                                Write-WithTime_Using_WriteHost "Restoring of VM extension '$($Extension.Name)' is supported."   

                                # SAP Extende monitoring extension
                                if ($Extension.Publisher -eq "Microsoft.AzureCAT.AzureEnhancedMonitoring") {
                                    Write-WithTime_Using_WriteHost "Restoring VM extension '$($Extension.Name)' on VM '$VirtualMachineName' in resource group '$ResourceGroupName' ..."   
                                    Set-AzVMAEMExtension -ResourceGroupName $ResourceGroupName -VMName $VirtualMachineName -InstallNewExtension > $null
                                }                             
                            }
                            else {
                                Write-WithTime_Using_WriteHost "Restoring of VM extension '$($Extension.Name)' is not supported. You need to reconfigure it manually." -Level "WARN"
                            }
                        }                                  
                                                                            
                    }
                    catch{
                       Write-Error  $_.Exception.Message                          
                   }
                   
                   finally {
                    
                   }
                }
            
                END {}
}

Function Resize-AzVM {
    <#
        .SYNOPSIS
        Resize-AzVM.
     
        .DESCRIPTION
        Resize-AzVM resizes Azure VM.
 
        If source has temp disk and target VM has no temp disk (or vice versa):
        - OS disk will be snapshoted.
        - From disk snapshot, OS disk copy will be executed with naming convention <OriginalOSDiskName>-orig<FreeNumber>
        - VM will be deleted
        - OS disk will be deleted
        - new OS disk with the <OriginalOSDiskName> name will be created
        - VM will be recreated with new VM SKU
         
        .PARAMETER VMResourceGroupName
        Virtual Machine Resource Group Name
                 
        .PARAMETER VirtualMachineName
        Virtual Machine Name
 
        .PARAMETER SubscriptionId
        You can specify explictly an Subscription Id. This is an optional paramater.
 
        .PARAMETER LogDirectory
        During the cmdlet runtime, cdlet is writting an log file in the current cmdlet directory. If you want o place log file in the different folder, you can specify it with '-LogDirectory' parameter.
        This is an optional parameter.
                        
        .PARAMETER Force
        Forces the command to run without asking for user confirmation.
     
        .EXAMPLE
        # Resize VM to the new SKU Standard_E4s_v5
         
        $ResourceGroupName = "sap-ab1"
        $VMName = "ab1-ascs"
 
        Resize-AzVM -ResourceGroupName $ResourceGroupName -VirtualMachineName $VMName -NewVMSKU Standard_E4s_v5
         
        .EXAMPLE
        # Resize VM to the new SKU Standard_E4s_v5 without confirmations
         
        $ResourceGroupName = "sap-ab1"
        $VMName = "ab1-ascs"
 
        Resize-AzVM -ResourceGroupName $ResourceGroupName -VirtualMachineName $VMName -NewVMSKU Standard_E4s_v5 -Force
 
        .EXAMPLE
        # Script will write log files named '<VMName>_<ResourceGroupName_<SubscriptionId>_<Year>_<Month>_<Day>_<Hour>_<Minute>.log'
        # Default option location is current script runtime directory
        # If you want to choose a difernt directory location, you can specify it using '-LogingDirectory' parameter
         
        $ResourceGroupName = "sap-ab1"
        $VMName = "ab1-ascs"
        # Windows PowerShell runtime
        $LogingDirectory = "c:\scriptlog"
        # Linux PowerShell runtime (such as Azure Portal Cloud Shell)
        # $LogingDirectory = "/home/user1/logdirectory"
 
        Resize-AzVM -ResourceGroupName $ResourceGroupName -VirtualMachineName $VMName -NewVMSKU Standard_E4s_v5 -LogDirectory $LogingDirectory -Force
 
        .EXAMPLE
        # Resize VM to the new SKU Standard_E4s_v5 in subscription Id f783cc2d-962b-4be1-b636-bbd9e4c60h84
         
        $ResourceGroupName = "sap-ab1"
        $VMName = "ab1-ascs"
        $SubscriptionId = "f783cc2d-962b-4be1-b636-bbd9e4c60h84"
 
        Resize-AzVM -ResourceGroupName $ResourceGroupName -VirtualMachineName $VMName -NewVMSKU Standard_E4s_v5 -SubscriptionId $SubscriptionId
         
        .LINK
         
        .NOTES
            v0.1 - Initial version
         
    #>

        
        #Requires -Modules Az.Compute
        #Requires -Version 5.1
        
            
                [CmdletBinding()]
                param(
                                    
                    [Parameter(Mandatory=$True)]                    
                    [ValidateNotNullOrEmpty()]        
                    [string] $ResourceGroupName,
                                              
                    [Parameter(Mandatory=$True)]
                    [ValidateNotNullOrEmpty()]        
                    [string] $VirtualMachineName,
                                                                                               
                    [Parameter(Mandatory=$True)]
                    [ValidateNotNullOrEmpty()]        
                    $NewVMSKU,

                    [Parameter(Mandatory=$False)]
                    [string] $SubscriptionId,

                    [Parameter(Mandatory=$False)]
                    [string] $LogDirectory,
                                                          
                    [switch] $Force                
                )
            
                BEGIN{
                     # Start logging
                     if ($LogDirectory) {                            
                        $LogFilePath = Get-AzVMScriptLogFileFullPath -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName -LogDirectory $LogDirectory    
                    }else {                            
                        $LogFilePath = Get-AzVMScriptLogFileFullPath -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName
                    }
                }
                
                PROCESS{
                    try{                
                                                   
                        # get start time
                        $StartTime = Get-Date   
                        
                        Start-Transcript -Path $LogFilePath -Append -ErrorAction Stop

                        # Set Subscription Id
                        if ($SubscriptionId) {                            
                            Write-WithTime_Using_WriteHost "Setting context to Azure subscription: $SubscriptionId ..."
                            Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop > $null
                            $RestoreCmdlet = "Restore-AzResizedVM -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName -SubscriptionId $SubscriptionId"
                        }else {
                            $RestoreCmdlet = "Restore-AzResizedVM -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName"
                        }

                        Write-WithTime_Using_WriteHost "Starting resizing of Virtual Machine '$VirtualMachineName' in resource group '$ResourceGroupName' ..."        

                        # Validate if VM is ready for resize
                        if ($SubscriptionId) {
                            [bool] $IsGoodToResize = Invoke-AzResizeAzVMValidation -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName -NewVMSKU $NewVMSKU -SubscriptionId $SubscriptionId
                        }else {
                            [bool] $IsGoodToResize = Invoke-AzResizeAzVMValidation -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName -NewVMSKU $NewVMSKU
                        }                    
                                                                        
                        if (-not $IsGoodToResize) {
                            Return
                        }
                        
                        # Get configuration
                        $originalVM     = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -ErrorAction Stop   
                        $VMIsZonal      = Test-AzVMIsZonalVM -ResourceGroupName $ResourceGroupName  -VirtualMachineName $VirtualMachineName   
                        #$VMExtensions = $originalVM.Extensions

                        # Do not delete OS and Data disks during the VM deletion - set DeleteOption to 'Detach'
                        Set-AzVMDisksDeleteOption -VM $originalVM -DeleteOption "Detach" -ErrorAction Stop                              
    
                        # Do not delete NIC cards during the VM deletion - Set NIC Cards to 'Detach'
                        Set-AzVMNICsDeleteOption -VM $originalVM -DeleteOption "Detach" -ErrorAction Stop    
                                               
                        # Shutdown the original VM
                        $ToStop = $true

                        if(-not $Force){                    
                            $ToStop = Get-AzVMStopAnswer -VirtualMachineName $VirtualMachineName -ResourceGroupName $ResourceGroupName
                        }                                        

                        if($ToStop){                    
                            Write-WithTime_Using_WriteHost  "Stopping virtual machine '$VirtualMachineName' in resource group '$ResourceGroupName' ..."
                            $ReturnStopVM =  Stop-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -force -ErrorAction Stop                             
                        }elseif (!$ToStop) {                               
                            Return                 
                        }                                                             
                        
                        # Get the OS Disk info
                        $OSDiskInfo = Get-AzVMOSDiskInfo -VM $originalVM -ErrorAction Stop
                        
                        # Create OS Snapshot
                        $snapshot = New-AzUniqueNameSnapshot -ResourceGroupName $OSDiskInfo.OSDisk.ResourceGroupName -Location $OSDiskInfo.Location -Disk $OSDiskInfo.OSDisk -ErrorAction Stop

                        # Create backup copy of OS disk
                        Copy-AzUniqueNameDiskFromSnapshot -ResourceGroupName $ResourceGroupName -Location $OSDiskInfo.Location -Snapshot $snapshot -OriginalDiskName $OSDiskInfo.OSDiskName -StorageType $OSDiskInfo.StorageType -VMDiskZone $OSDiskInfo.OSDiskZone -DiskNamePosfix "orig" -Tags $OSDiskInfo.OSDiskTags

                        # Get the file names for export/import/restore tasks
                        $GenerateFilesPathNames =  Get-AzVMUniqueueExportImportJSONFileNamePath -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName                        
                        
                        # Export Original VM in ARM
                        Write-WithTime_Using_WriteHost  "Exporting Azure VM ARM template to '$($GenerateFilesPathNames.OriginalARMVMFullPathFile)' file ..."
                        Export-AzResourceGroup -ResourceGroupName $originalVM.ResourceGroupName  -Resource $originalVM.Id -Path $GenerateFilesPathNames.OriginalARMVMFullPathFile -SkipAllParameterization -Force -ErrorAction Stop > $null

                        # Export Original VM in PowerShell JSON object
                        Write-WithTime_Using_WriteHost  "Exporting Azure VM PowerShell configuration to '$($GenerateFilesPathNames.OriginalPowerShellVMFullPathFile)' ..."                        
                        $originalVM | ConvertTo-Json -Depth 100 | Out-File $GenerateFilesPathNames.OriginalPowerShellVMFullPathFile -ErrorAction Stop
                        
                        # Create NEW ARM template
                        $VMImportedFromJSONObject = Update-AzVMARMTemplateForReDeploy -SourceVMFullPathFile $GenerateFilesPathNames.OriginalARMVMFullPathFile -NewVMSKU $NewVMSKU
                                                                        
                        # Save new JSON File
                        Write-WithTime_Using_WriteHost  "Exporting new updated Azure VM ARM template to '$($GenerateFilesPathNames.NewARMVMFullPathFile)' ..."
                        $VMImportedFromJSONObject | ConvertTo-Json  -Depth 100 | Out-File $GenerateFilesPathNames.NewARMVMFullPathFile                        

                        # Export OS disk restore info
                        Write-WithTime_Using_WriteHost  "Exporting Azure VM restore information to file '$($GenerateFilesPathNames.VMOSDiskRestore)' ..."
                        $VMOSDiskRestoreInfo = Get-AzVMRestoreInformation -VM $originalVM -OSDiskSnapshotId $snapshot.Id -OSDiskInfo $OSDiskInfo
                        $VMOSDiskRestoreInfo  | ConvertTo-Json  -Depth 100 | Out-File $GenerateFilesPathNames.VMOSDiskRestore                                                

                        # Remove the original VM -this is a prerequisit to delete orignial OS and data disks
                        $ToDelete = $true
        
                        if(-not $Force){                        
                            $ToDelete = Get-AzVMDeleteAnswer -VirtualMachineName $VirtualMachineName -ResourceGroupName $ResourceGroupName
                        }
                        
                        if($ToDelete){                        
                            Write-WithTime_Using_WriteHost  "Starting recreating of the VM '$VirtualMachineName' in the resource group '$ResourceGroupName' ..."
                            Write-WithTime_Using_WriteHost  "If VM recreate process fails, you can restore VM '$VirtualMachineName' to original state by running command '$RestoreCmdlet'." -Level "WARN"
                            Write-WithTime_Using_WriteHost  "Removing virtual machine '$VirtualMachineName' ..."                            
                            Remove-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -Force -ErrorAction Stop > $null        
                        }else {
                            # Exit
                            Return   
                        }            

                        # Delete Original OS Disk
                        Remove-AzDisk -ResourceGroupName $ResourceGroupName -DiskName  $OSDiskInfo.OSDiskName -Force -ErrorAction Stop > $null 
                        
                        if($OSDiskInfo.OSDisk.PurchasePlan){
                            Write-WithTime_Using_WriteHost "Original OS disk has purchase plan: Name '$($OSDiskInfo.OSDisk.PurchasePlan.Name)', Publisher '$($OSDiskInfo.OSDisk.PurchasePlan.Publisher)', Product '$($OSDiskInfo.OSDisk.PurchasePlan.Product)', PromotionCode '$($OSDiskInfo.OSDisk.PurchasePlan.PromotionCode)'."
                            $OSDiskPurchasePlan = $OSDiskInfo.OSDisk.PurchasePlan
                            $diskPurchasePlan   = New-AzDiskPurchasePlanConfig -Name  $OSDiskInfo.OSDisk.PurchasePlan.Name -Publisher  $OSDiskInfo.OSDisk.PurchasePlan.Publisher -Product  $OSDiskInfo.OSDisk.PurchasePlan.Product  # -PromotionCode $OSDisk.PurchasePlan.PromotionCode
                            
                            if ($VMIsZonal) {
                                $newdiskConfig  = New-AzDiskConfig -AccountType $OSDiskInfo.StorageType -Location $OSDiskInfo.Location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $OSDiskInfo.OSDiskTags -PurchasePlan $diskPurchasePlan -Zone $OSDiskInfo.OSDiskZone
                            }else {
                                $newdiskConfig  = New-AzDiskConfig -AccountType $OSDiskInfo.StorageType -Location $OSDiskInfo.Location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $OSDiskInfo.OSDiskTags -PurchasePlan $diskPurchasePlan
                            }                                                    
                        }else{
                            Write-WithTime_Using_WriteHost "OS disk has NO purchase plan"
                            if ($VMIsZonal) {                                
                                $newdiskConfig = New-AzDiskConfig -AccountType $OSDiskInfo.StorageType -Location $OSDiskInfo.Location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $OSDiskInfo.OSDiskTags -Zone $OSDiskInfo.OSDiskZone
                            }else {
                                $newdiskConfig = New-AzDiskConfig -AccountType $OSDiskInfo.StorageType -Location $OSDiskInfo.Location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $OSDiskInfo.OSDiskTags    
                            }                            
                        }
                                            
                        Write-WithTime_Using_WriteHost  "Creating OS disk '$($OSDiskInfo.OSDiskName)' from snapshot '$($snapshot.Name)' ..."
                        $newdisk = New-AzDisk -Disk $newdiskConfig -ResourceGroupName $ResourceGroupName -DiskName $OSDiskInfo.OSDiskName -ErrorAction Stop

                        # Recreate VM from updated JSON
                        Write-WithTime_Using_WriteHost  "Recreating VM from the Azure ARM template file '$($GenerateFilesPathNames.NewARMVMFullPathFile)' ..."
                        New-AzResourceGroupDeployment -Name $VirtualMachineName -ResourceGroupName $ResourceGroupName -TemplateFile $GenerateFilesPathNames.NewARMVMFullPathFile -Verbose -ErrorAction Stop > $null                  
                        
                        Write-WithTime_Using_WriteHost "Virtual Machine '$VirtualMachineName' in resource group '$ResourceGroupName' is recreated with the new VM SKU '$NewVMSKU'."    
                        
                        Write-WithTime_Using_WriteHost "Executing post configuration tasks ..."     
                        
                        # Reinstall extensions
                        if ($originalVM.Extensions) {     
                            Write-WithTime_Using_WriteHost "Restoring virtual machine '$VirtualMachineName' VM extensions ..."                          
                            Restore-AzVMExtensions -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName -VMExtensions $originalVM.Extensions
                        }

                        Write-WithTime_Using_WriteHost "Resizing of Virtual Machine '$VirtualMachineName' in resource group '$ResourceGroupName' from VM SKU '$($originalVM.HardwareProfile.VmSize)' to the new VM SKU '$NewVMSKU' is succesfully finished."     
                    }
                    catch{
                       Write-Error  $_.Exception.Message                          
                   }
                   
                   finally {                    
                    $EndTime = Get-Date
                    $ElapsedTime = $EndTime - $StartTime
                    Write-WithTime_Using_WriteHost "Total time : $($ElapsedTime.Days) days, $($ElapsedTime.Hours) hours, $($ElapsedTime.Minutes) minutes, $($ElapsedTime.Seconds) seconds, $($ElapsedTime.Seconds) milliseconds."
                    
                    Stop-Transcript        
                   }
                }
            
                END {}
}
    
Function Restore-AzResizedVM {
    <#
        .SYNOPSIS
        Restore-AzResizedVM restores an VM in case of failure during the running of Resize-AzVM.
     
        .DESCRIPTION
        Restore-AzResizedVM restores an VM in case of failure during the running of Resize-AzVM.
         
        .PARAMETER VMResourceGroupName
        Virtual Machine Resource Group Name
                 
        .PARAMETER VirtualMachineName
        Virtual Machine Name
 
        .PARAMETER SubscriptionId
        You can specify explictly an Subscription Id. This is an optional paramater.
 
        .PARAMETER LogDirectory
        During the cmdlet runtime, cdlet is writting an log file in the current cmdlet directory. If you want o place log file in the different folder, you can specify it with '-LogDirectory' parameter.
        This is an optional parameter.
             
        .PARAMETER Force
        Forces the command to run without asking for user confirmation.
             
        .EXAMPLE
        # Restore VM to the original SKU SKU
        # You are runnign this cmdlet in case of failure by VM recreation e.g. during the running of resize cmdlet:
        # Resize-AzVM -ResourceGroupName $ResourceGroupName -VirtualMachineName $VMName -NewVMSKU Standard_E4s_v5 -Force
         
        $ResourceGroupName = "sap-ab1"
        $VMName = "ab1-ascs"
 
        Restore-AzResizedVM -ResourceGroupName $ResourceGroupName -VirtualMachineName $VMName
         
        .LINK
         
        .NOTES
            v0.1 - Initial version
         
    #>

        
        #Requires -Modules Az.Compute
        #Requires -Version 5.1
        
            
                [CmdletBinding()]
                param(
                                    
                    [Parameter(Mandatory=$True)]                    
                    [ValidateNotNullOrEmpty()]        
                    [string] $ResourceGroupName,
                                              
                    [Parameter(Mandatory=$True)]
                    [ValidateNotNullOrEmpty()]        
                    [string] $VirtualMachineName,                                                                                                                   

                    [Parameter(Mandatory=$False)]
                    [string] $SubscriptionId,

                    [Parameter(Mandatory=$False)]
                    [string] $LogDirectory,
                                                          
                    [switch] $Force                
                )
            
                BEGIN{
                }
                
                PROCESS{
                    try{                

                        # get start time
                        $StartTime = Get-Date  
                                                                        
                        # Set Subscription Id
                        if ($SubscriptionId) {                            
                            Write-WithTime_Using_WriteHost "Setting context to Azure subscription: $SubscriptionId ..."
                            Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop > $null
                        }

                        # Start logging
                        if ($LogDirectory) {                            
                            $LogFilePath = Get-AzVMScriptLogFileFullPath -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName -LogDirectory $LogDirectory    
                        }else {                            
                            $LogFilePath = Get-AzVMScriptLogFileFullPath -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName
                        }
                        
                        Start-Transcript -Path $LogFilePath -Append -ErrorAction Stop

                        Write-WithTime_Using_WriteHost "Starting restoring of Virtual Machine '$VirtualMachineName' in resource group '$ResourceGroupName' ..." 

                        # get Azure subscription information
                        Write-WithTime_Using_WriteHost "Getting Azure subscription information ..."
                        $SubscriptionContext = Get-AzSubscriptionContext
                        Write-WithTime_Using_WriteHost "Azure subscription name: '$($SubscriptionContext.Name)', Subscription Id: '$($SubscriptionContext.SubscriptionId)', Tenant Id: '$($SubscriptionContext.TenantId)'."   
                        
                        # Get the file names for export/import/restore tasks
                        $GenerateFilesPathNames =  Get-AzVMUniqueueExportImportJSONFileNamePath -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName   

                        Write-WithTime_Using_WriteHost "Importing VM '$VirtualMachineName' restore file '$($GenerateFilesPathNames.VMOSDiskRestore)' ..."   
                        $VMImportedRestoreFileFromJSONObject = Get-Content $GenerateFilesPathNames.VMOSDiskRestore -ErrorAction Stop | ConvertFrom-Json
                        
                        # Check if resource group exists
                        Write-WithTime_Using_WriteHost "Checking if resource group '$ResourceGroupName' exists ..."
                        $VMResourceGroupExist = Test-AzResourceGroupExist -ResourceGroupName $ResourceGroupName
                        if ($VMResourceGroupExist) {
                            Write-WithTime_Using_WriteHost "Resource group '$ResourceGroupName' exists."
                        }else {
                            Throw "Azure resource group '$ResourceGroupName' do not exist. Please check your resource group '$ResourceGroupName' name."
                        }

                        # Check if VM exist
                        Write-WithTime_Using_WriteHost "Checking if virtual machine '$VirtualMachineName' in resource group '$ResourceGroupName' exists ..."                        
                        $VMExist = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -ErrorAction SilentlyContinue

                        if ($VMExist) {
                            Write-WithTime_Using_WriteHost "Virtual machine '$VirtualMachineName' in resource group '$ResourceGroupName' exists."

                            Write-WithTime_Using_WriteHost  "Getting virtual machine '$VirtualMachineName' configuration ..."
                            $originalVM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -ErrorAction Stop   

                            if ($originalVM.HardwareProfile.VmSize -eq $VMImportedRestoreFileFromJSONObject.HardwareProfile.VmSize) {
                                Throw "VM is already set to original SKU '$($VMImportedRestoreFileFromJSONObject.HardwareProfile.VmSize)'. There is no need to restore it."                                
                                #return
                                #exit
                            }else {                                
                                # Remove the original VM
                                $ToDelete = $true
                
                                if(-not $Force){                        
                                    $ToDelete = Get-AzVMDeleteAnswer -VirtualMachineName $VirtualMachineName -ResourceGroupName $ResourceGroupName
                                }
                                
                                if($ToDelete){                        
                                    Write-WithTime_Using_WriteHost  "Removing virtual machine '$VirtualMachineName' ..."                                    
                                    Remove-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -Force -ErrorAction Stop > $null        
                                }else {
                                    # Exit
                                    Return   
                                }            
                            }
                        }else {
                            Write-WithTime_Using_WriteHost "Virtual machine '$VirtualMachineName' in resource group '$ResourceGroupName' do not exists."

                            # Check OS disk
                            $DiskName = $VMImportedRestoreFileFromJSONObject.StorageProfile.OsDisk.Name
                            Write-WithTime_Using_WriteHost  "Checking existance of the OS disk '$DiskName' in resource group '$ResourceGroupName' ..."                                                    
                            $OSDiskExist = Test-AzDiskExist -ResourceGroupName $VMImportedRestoreFileFromJSONObject.OSDiskInfo.OSDisk.ResourceGroupName -DiskName $VMImportedRestoreFileFromJSONObject.StorageProfile.OsDisk.Name

                            if ($OSDiskExist) {
                                Write-WithTime_Using_WriteHost  "OS disk '$DiskName' in resource group '$ResourceGroupName' exists."
                            }else {
                                Write-WithTime_Using_WriteHost  "OS disk '$DiskName' in resource group '$ResourceGroupName' do not exists."

                                # Check Snapshot - BREAK if missing!
                                Write-WithTime_Using_WriteHost  "Getting OS disk '$DiskName' snapshot with Id '$($VMImportedRestoreFileFromJSONObject.StorageProfile.OsDisk.OSDiskSnapshotId)' ..."

                                # Get the snapshot Name and ResourceGroupName
                                $VMOSSnapshot = Get-AzResource -ResourceId $VMImportedRestoreFileFromJSONObject.StorageProfile.OsDisk.OSDiskSnapshotId -ErrorAction Stop

                                # Get snapshot
                                $snapshot = Get-AzSnapshot -ResourceGroupName $VMOSSnapshot.ResourceGroupName -SnapshotName $VMOSSnapshot.Name -ErrorAction Stop

                                # Create new OS disk from snapshot
                                
                                # Get the OS Disk info
                                $OSDiskInfo = $VMImportedRestoreFileFromJSONObject.OSDiskInfo
                                $VMIsZonal  = $VMImportedRestoreFileFromJSONObject.Zones

                                # Convert PS Object to Hash table
                                $TagsHashTable = @{}
                                $OSDiskInfo.OSDiskTags.PSObject.Properties | ForEach-Object {
                                    $TagsHashTable[$_.Name] = $_.Value
                                }

                                if($OSDiskInfo.OSDisk.PurchasePlan){
                                    Write-WithTime_Using_WriteHost "Original OS disk has purchase plan: Name '$($OSDiskInfo.OSDisk.PurchasePlan.Name)', Publisher '$($OSDiskInfo.OSDisk.PurchasePlan.Publisher)', Product '$($OSDiskInfo.OSDisk.PurchasePlan.Product)', PromotionCode '$($OSDiskInfo.OSDisk.PurchasePlan.PromotionCode)'."
                                    $OSDiskPurchasePlan = $OSDiskInfo.OSDisk.PurchasePlan
                                    $diskPurchasePlan   = New-AzDiskPurchasePlanConfig -Name  $OSDiskInfo.OSDisk.PurchasePlan.Name -Publisher  $OSDiskInfo.OSDisk.PurchasePlan.Publisher -Product  $OSDiskInfo.OSDisk.PurchasePlan.Product  # -PromotionCode $OSDisk.PurchasePlan.PromotionCode
                                    
                                    if ($VMIsZonal) {
                                        $newdiskConfig  = New-AzDiskConfig -AccountType $OSDiskInfo.StorageType -Location $OSDiskInfo.Location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $TagsHashTable -PurchasePlan $diskPurchasePlan -Zone $OSDiskInfo.OSDiskZone
                                    }else {
                                        $newdiskConfig  = New-AzDiskConfig -AccountType $OSDiskInfo.StorageType -Location $OSDiskInfo.Location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $TagsHashTable -PurchasePlan $diskPurchasePlan
                                    }                                                    
                                }else{
                                    Write-WithTime_Using_WriteHost "OS disk has NO purchase plan"
                                    if ($VMIsZonal) {                                
                                        $newdiskConfig = New-AzDiskConfig -AccountType $OSDiskInfo.StorageType -Location $OSDiskInfo.Location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $TagsHashTable -Zone $OSDiskInfo.OSDiskZone
                                    }else {
                                        $newdiskConfig = New-AzDiskConfig -AccountType $OSDiskInfo.StorageType -Location $OSDiskInfo.Location -CreateOption Copy -SourceResourceId $snapshot.Id -Tag $TagsHashTable    
                                    }                            
                                }
                                                    
                                Write-WithTime_Using_WriteHost  "Creating OS disk '$($OSDiskInfo.OSDiskName)' from snapshot '$($snapshot.Name)' ..."
                                $newdisk = New-AzDisk -Disk $newdiskConfig -ResourceGroupName $ResourceGroupName -DiskName $OSDiskInfo.OSDiskName -ErrorAction Stop

                            }                                                    
                        }

                        $SourceVMSKU = $VMImportedRestoreFileFromJSONObject.HardwareProfile.VmSize  
                        Write-WithTime_Using_WriteHost "Virtual machine '$VirtualMachineName' in resource group '$ResourceGroupName' original SKU is '$SourceVMSKU'."                      
                        # get new updated ARM JSON template with Original VM SKU
                        $UpdatedOriginalARMTemplate = Update-AzVMARMTemplateForReDeploy -SourceVMFullPathFile $GenerateFilesPathNames.OriginalARMVMFullPathFile -NewVMSKU $SourceVMSKU
                                                                        
                        # Save to a new ARM template JSON File
                        Write-WithTime_Using_WriteHost  "Exporting new updated Azure VM ARM template to '$($GenerateFilesPathNames.OriginalRestoreARMVMFullPathFile)' ..."
                        $UpdatedOriginalARMTemplate | ConvertTo-Json  -Depth 100 | Out-File $GenerateFilesPathNames.OriginalRestoreARMVMFullPathFile                        

                        # Recreate VM from updated JSON
                        Write-WithTime_Using_WriteHost  "Restoring VM to the original VM SKU '$SourceVMSKU' from the Azure ARM template file '$($GenerateFilesPathNames.OriginalRestoreARMVMFullPathFile)' ..."
                        New-AzResourceGroupDeployment -Name $VirtualMachineName -ResourceGroupName $ResourceGroupName -TemplateFile $GenerateFilesPathNames.OriginalRestoreARMVMFullPathFile -Verbose -ErrorAction Stop > $null         
                        
                        Write-WithTime_Using_WriteHost "Virtual Machine '$VirtualMachineName' in resource group '$ResourceGroupName' is restored to the original VM SKU '$SourceVMSKU'."    
                        
                        Write-WithTime_Using_WriteHost "Executing post configuration tasks ..."
                        
                        # Reinstall extensions
                        if ($VMImportedRestoreFileFromJSONObject.Extensions) {     
                            Write-WithTime_Using_WriteHost "Restoring virtual machine '$VirtualMachineName' VM extensions ..."
                            Restore-AzVMExtensions -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName -VMExtensions $VMImportedRestoreFileFromJSONObject.Extensions
                        }
                        
                        Write-WithTime_Using_WriteHost "Restore of the virtual machine '$VirtualMachineName' in resource group '$ResourceGroupName' to the original VM SKU '$SourceVMSKU ' is succesfully finished."                                                                                
                    }
                    catch{
                       Write-Error  $_.Exception.Message                          
                   }
                   
                   finally {
                    $EndTime = Get-Date
                    $ElapsedTime = $EndTime - $StartTime
                    Write-WithTime_Using_WriteHost "Total time : $($ElapsedTime.Days) days, $($ElapsedTime.Hours) hours, $($ElapsedTime.Minutes) minutes, $($ElapsedTime.Seconds) seconds, $($ElapsedTime.Seconds) milliseconds."
                    
                    Stop-Transcript        
                   }
                }
            
                END {}
}
Function Invoke-AzResizeAzVMValidation {
    <#
        .SYNOPSIS
        Invoke-AzResizeAzVMValidation validate if an VM is ready to migrate to new VM SKU.
     
        .DESCRIPTION
        Invoke-AzResizeAzVMValidation validate if an VM is ready to migrate to new VM SKU. If VM is ready for migration, it returns $true, otherwise $false.
 
         
        .PARAMETER VMResourceGroupName
        Virtual Machine Resource Group Name
                 
        .PARAMETER VirtualMachineName
        Virtual Machine Name
 
        .PARAMETER SubscriptionId
        You can specify explictly an Subscription Id. This is an optional paramater.
     
        .EXAMPLE
     
        $ResourceGroupName = "sap-ab1"
        $VMName = "ab1-ascs"
        $NewVMSKU = "Standard_E4s_v5"
 
        [bool] $ret = Invoke-AzResizeAzVMValidation -ResourceGroupName $ResourceGroupName -VirtualMachineName $VMName -NewVMSKU $NewVMSKU
         
         
        .LINK
         
        .NOTES
            v0.1 - Initial version
         
    #>

        
        #Requires -Modules Az.Compute
        #Requires -Version 5.1
        
            
                [CmdletBinding()]
                param(
                                    
                    [Parameter(Mandatory=$True)]                    
                    [ValidateNotNullOrEmpty()]        
                    [string] $ResourceGroupName,
                                              
                    [Parameter(Mandatory=$True)]
                    [ValidateNotNullOrEmpty()]        
                    [string] $VirtualMachineName,
                                                                                               
                    [Parameter(Mandatory=$True)]
                    [ValidateNotNullOrEmpty()]        
                    $NewVMSKU,

                    [Parameter(Mandatory=$False)]
                    [string] $SubscriptionId                   
                )
            
                BEGIN{
                }
                
                PROCESS{
                    try{                
                                
                        [bool] $ResizePrerequisitsFulfilled = $true

                        # Set Subscription Id
                        if ($SubscriptionId) {                            
                            Write-WithTime_Using_WriteHost "Setting context to Azure subscription: $SubscriptionId ..."
                            Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop > $null                            
                        }                        
                        
                        # get Azure subscription information
                        Write-WithTime_Using_WriteHost "Getting Azure subscription information ..."
                        $SubscriptionContext = Get-AzSubscriptionContext
                        Write-WithTime_Using_WriteHost "Azure subscription name: '$($SubscriptionContext.Name)', Subscription Id: '$($SubscriptionContext.SubscriptionId)', Tenant Id: '$($SubscriptionContext.TenantId)'."                        

                        # Check if resource group exists
                        Write-WithTime_Using_WriteHost "Checking if resource group '$ResourceGroupName' exists ..."
                        $VMResourceGroupExist = Test-AzResourceGroupExist -ResourceGroupName $ResourceGroupName
                        if ($VMResourceGroupExist) {
                            Write-WithTime_Using_WriteHost "Resource group '$ResourceGroupName' exists."
                        }else {
                            Throw "Azure resource group '$ResourceGroupName' do not exist. Please check your resource group '$ResourceGroupName' name."
                        }

                        # Check if VM exist
                        Write-WithTime_Using_WriteHost "Checking if virtual machine '$VirtualMachineName' in resource group '$ResourceGroupName' exists ..."
                        $VMExist = Test-AzVirtualMachineExist -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName
                        if ($VMExist) {
                            Write-WithTime_Using_WriteHost "Virtual machine '$VirtualMachineName' in resource group '$ResourceGroupName' exists."
                        }else {
                            Throw "Virtual machine '$VirtualMachineName' in resource group '$ResourceGroupName' do not exists. Please check virtual machine name '$VirtualMachineName' and resource group name '$ResourceGroupName'."
                        }

                        # CHeck if VM is running
                        Write-WithTime_Using_WriteHost "Checking if virtual machine '$VirtualMachineName' in resource group '$ResourceGroupName' is running ..."
                        $VMIsRunning = Test-AzVMIsStarted -ResourceGroupName  $ResourceGroupName -VMName $VirtualMachineName
            
                        if ($VMIsRunning -eq $True) {                    
                            Write-WithTime_Using_WriteHost "Virtual machine '$VirtualMachineName' in resource group '$ResourceGroupName' is running."
                        }else {
                            Write-WithTime_Using_WriteHost  "Virtual machine '$VirtualMachineName' is not running."
                            Write-WithTime_Using_WriteHost  "Starting virtual machine '$VirtualMachineName' ..."
                            $StartVMStatus = Start-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -ErrorAction Stop 
                        }                                                
    
                        Write-WithTime_Using_WriteHost  "Getting virtual machine '$VirtualMachineName' configuration ..."
                        $originalVM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -ErrorAction Stop   

                        # Source and Target VM SKU must be different
                        if ($originalVM.HardwareProfile.VmSize -eq $NewVMSKU) {
                            Throw "Source and new target VM SKU have the same value '$NewVMSKU'. No need to resize the VM '$VirtualMachineName' in resource group '$ResourceGroupName'."                                                                                    
                        }

                        # Print VM Extensions
                        Write-WithTime_Using_WriteHost "Getting virtual machine '$VirtualMachineName' extensions ...."                        
                        $VMExtensions = $originalVM.Extensions
                        if ($VMExtensions) {
                            Write-WithTime_Using_WriteHost "Virtual machine '$VirtualMachineName' has configured $($VMExtensions.count) extension(s)."
                            Write-AzVMExtensions $VMExtensions                            
                        }else {
                            Write-WithTime_Using_WriteHost "Virtual machine '$VirtualMachineName' has no configured extensions."
                        }
                        
                        # Check VM PPG
                        Write-WithTime_Using_WriteHost "Checking if VM '$VirtualMachineName' in resource group '$ResourceGroupName' has configured Proximity Placement Group ..."
                        $VMHasPPG = Test-AzVMPPG -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName
                        if ($VMHasPPG) {
                            $VMPPG = Get-AzVmPPG -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName
                            Write-WithTime_Using_WriteHost "Virtual machine '$VirtualMachineName' in resource group '$ResourceGroupName' is configured with Proximity Placement Group (PPG) Id '$VMPPG'. PPG increase possibility of compute allocation failure during VM resize. It is highly recommended to remove PPG before continuing VM resize procedure!" -Level "WARN"
                        }else {
                            Write-WithTime_Using_WriteHost "Virtual machine '$VirtualMachineName' in resource group '$ResourceGroupName' has no configured with Proximity Placement Group."
                        }

                        Write-WithTime_Using_WriteHost "Resizing from '$VirtualMachineName' in resource group '$ResourceGroupName' from '$($originalVM.HardwareProfile.VmSize)' to '$NewVMSKU' SKU ..."

                        # Check if VM is regional or zonal
                        Write-WithTime_Using_WriteHost  "Checking if Virtual Machine '$VirtualMachineName' is zonal VM ..."
                        $VMIsZonal = Test-AzVMIsZonalVM -ResourceGroupName $ResourceGroupName  -VirtualMachineName $VirtualMachineName
                        if ($VMIsZonal) {
                            Write-WithTime_Using_WriteHost "Virtual Machine '$VirtualMachineName' is zonal VM in zone '$($originalVM.Zones)'."
                        }else {
                            Write-WithTime_Using_WriteHost "Virtual Machine '$VirtualMachineName' is regional VM."
                        }
                        
                        # Check target VM SKU regional or zonal availability
                        if ($VMIsZonal) {
                            Write-WithTime_Using_WriteHost  "Checking target VM SKU '$NewVMSKU' zonal availability in Azure region '$($originalVM.Location)' and zone '$($originalVM.Zones)' ..."
                            $VMSKUIsAvailable = Test-AzComputeSKUAvailability -Location $originalVM.Location -VMSKU $NewVMSKU -AzureZone $originalVM.Zones
                            if (-not $VMSKUIsAvailable) {
                                Throw "VM SKU '$NewVMSKU' is not available in the Azure region '$($originalVM.Location)' and zone '$($originalVM.Zones)'. Cannot resize Azure VM '$VirtualMachineName' to the new VM SKU '$NewVMSKU'!"
                            }else {
                                Write-WithTime_Using_WriteHost "VM SKU '$NewVMSKU' is available in the Azure region '$($originalVM.Location)' and zone '$($originalVM.Zones)'."
                            }
                            
                        }else {
                            Write-WithTime_Using_WriteHost  "Checking target VM SKU '$NewVMSKU' regional availability in Azure region '$($originalVM.Location)' ..."
                            $VMSKUIsAvailable = Test-AzComputeSKUAvailability -Location $originalVM.Location -VMSKU $NewVMSKU 
                            if (-not $VMSKUIsAvailable) {
                                Throw "VM SKU '$NewVMSKU' is not available in the Azure region '$($originalVM.Location)'. Cannot resize Azure VM '$VirtualMachineName' to the new VM SKU '$NewVMSKU'!"
                            }else {
                                Write-WithTime_Using_WriteHost "VM SKU '$NewVMSKU' is available in the Azure region '$($originalVM.Location)'."
                            }
                        }

                        # Check if new VM SKU supports current VM Hyper-V Generation
                        Write-WithTime_Using_WriteHost "Checking Hyper-V Generation of virtual machine '$VirtualMachineName' in resource group '$ResourceGroupName' ..." 
                        $VMStatus = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -Status
                        $CurrentVMHyperVGeneration = $VMStatus.HyperVGeneration
                        if ($CurrentVMHyperVGeneration -eq "V1") {
                                Write-WithTime_Using_WriteHost "Hyper-V Generation of virtual machine '$VirtualMachineName' in resource group '$ResourceGroupName' is 'V1' - VM is Generation 1." 
                            }elseif ($CurrentVMHyperVGeneration -eq "V2") {
                                Write-WithTime_Using_WriteHost "Hyper-V Generation of virtual machine '$VirtualMachineName' in resource group '$ResourceGroupName' is 'V2' - VM is Generation 2." 
                        }
                        
                        Write-WithTime_Using_WriteHost "Checking if new VM SKU '$NewVMSKU' supports current VM '$VirtualMachineName' Hyper-V Generation '$CurrentVMHyperVGeneration' ..."
                        $NEwVMSKUSupportsCurrentHyperVgeneration = Test-AzVMResizeSupportNewSKUGeneration -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName -VMSKU $NewVMSKU
                        if ($NEwVMSKUSupportsCurrentHyperVgeneration) {
                            Write-WithTime_Using_WriteHost "New VM SKU '$NewVMSKU' supports current VM '$VirtualMachineName' Hyper-V Generation '$CurrentVMHyperVGeneration'."
                        }else {
                            #Write-Error "New VM SKU '$NewVMSKU' do not supports current VM '$VirtualMachineName' Hyper-V Generation '$CurrentVMHyperVGeneration'."
                            Throw "New VM SKU '$NewVMSKU' do not supports current VM '$VirtualMachineName' Hyper-V Generation '$CurrentVMHyperVGeneration'. Please choose another VM SKU that supports VM Hyper-V generation '$CurrentVMHyperVGeneration'."
                        }
                        
                        # Check VMs SKU suport for temp disk
                        Write-WithTime_Using_WriteHost "Checking source VM SKU '$($originalVM.HardwareProfile.VmSize)' support for temp disk... "
                        $SourceVMSKUSupportForTempDisk = Test-AzVMSKUEphemeralOSDiskSupport -Location $originalVM.Location -VMSKU $originalVM.HardwareProfile.VmSize
                        if ($SourceVMSKUSupportForTempDisk -eq "True") {
                            Write-WithTime_Using_WriteHost "Source VM SKU '$($originalVM.HardwareProfile.VmSize)' supports temp disk."
                        }elseif (($SourceVMSKUSupportForTempDisk -eq "False")) {
                            Write-WithTime_Using_WriteHost "Source VM SKU '$($originalVM.HardwareProfile.VmSize)' do not support temp disk."
                        }

                        Write-WithTime_Using_WriteHost "Checking target VM SKU '$NewVMSKU' support for temp disk... "
                        $TargetVMSKUSupportForTempDisk = Test-AzVMSKUEphemeralOSDiskSupport -Location $originalVM.Location -VMSKU $NewVMSKU
                        if ($TargetVMSKUSupportForTempDisk -eq "True") {
                            Write-WithTime_Using_WriteHost "Target VM SKU '$NewVMSKU' supports temp disk."
                        }elseif (($TargetVMSKUSupportForTempDisk -eq "False")) {
                            Write-WithTime_Using_WriteHost "Target VM SKU '$NewVMSKU' do not support temp disk."
                        }

                        if ($SourceVMSKUSupportForTempDisk -eq "True" -and $TargetVMSKUSupportForTempDisk -eq "False") {                            
                            Write-WithTime_Using_WriteHost "Resizing from source VM SKU '$($originalVM.HardwareProfile.VmSize)' WITH temp disk, to target VM SKU '$NewVMSKU' WITHOUT temp disks requires that VM and OS disk are recreated, as described in the Azure documentation https://learn.microsoft.com/sl-si/azure/virtual-machines/azure-vms-no-temp-disk#how-do-i-migrate-from-a-vm-size-with-local-temp-disk-to-a-vm-size-with-no-local-temp-disk--- ..." -Level "WARN"
                            Write-WithTime_Using_WriteHost "If you have configured OS paging on the temp storage, it is highly advised to reconfigure OS paging on a persistent disk prior to VM SKU conversion, as described in the Azure documentation https://learn.microsoft.com/sl-si/azure/virtual-machines/windows/change-drive-letter." -Level "WARN"
                        }elseif ($SourceVMSKUSupportForTempDisk -eq "False" -and $TargetVMSKUSupportForTempDisk -eq "True") {                            
                            Write-WithTime_Using_WriteHost "Resizing from source VM SKU '$($originalVM.HardwareProfile.VmSize)' WITHOUT temp disk, to target VM SKU '$NewVMSKU' WITH temp disks requires that VM and OS disk are recreated, as described in the Azure documentation https://learn.microsoft.com/sl-si/azure/virtual-machines/azure-vms-no-temp-disk#how-do-i-migrate-from-a-vm-size-with-local-temp-disk-to-a-vm-size-with-no-local-temp-disk--- ..." -Level "WARN"
                        }                                                
                    }
                    catch{
                        [bool] $ResizePrerequisitsFulfilled = $false
                        Write-Error  $_.Exception.Message                                                  
                   }
                   
                   finally {
                    Write-Output $ResizePrerequisitsFulfilled
                   }
                }
            
                END {}
}


####



Function Get-AzVMHyperVGeneration {
    <#
        .SYNOPSIS
        Get-AzVMHyperVGeneration gets an VM Hyper-V Generation version - V1 or V2.
     
        .DESCRIPTION
        Get-AzVMHyperVGeneration gets an VM Hyper-V Generation version - V1 or V2.
 
         
        .PARAMETER VMResourceGroupName
        Virtual Machine Resource Group Name
                 
        .PARAMETER VirtualMachineName
        Virtual Machine Name
 
        .PARAMETER SubscriptionId
        You can specify explictly an Subscription Id. This is an optional paramater.
     
        .EXAMPLE
        # get Hyperv Genrreation version for VM 'vm1' in resource group 'my-resourcegroup1'
 
        Get-AzVMHyperVGeneration -ResourceGroupName my-resourcegroup1 -VirtualMachineName vm1
                 
        .LINK
         
        .NOTES
            v0.1 - Initial version
         
    #>

        
        #Requires -Modules Az.Compute
        #Requires -Version 5.1
        
            
                [CmdletBinding()]
                param(
                                    
                    [Parameter(Mandatory=$True)]                    
                    [ValidateNotNullOrEmpty()]        
                    [string] $ResourceGroupName,
                                              
                    [Parameter(Mandatory=$True)]
                    [ValidateNotNullOrEmpty()]        
                    [string] $VirtualMachineName,
                                                                                                                   
                    [Parameter(Mandatory=$False)]
                    [string] $SubscriptionId                   
                )
            
                BEGIN{
                }
                
                PROCESS{
                    try{                                                                       

                        Write-Verbose "Executing Get-AzVMHyperVGeneration ..."

                        # Set Subscription Id
                        if ($SubscriptionId) {                            
                            Write-Verbose "Setting context to Azure subscription: $SubscriptionId ..."
                            Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop > $null                            
                        }           
                        
                        $VMStatus = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -Status
                        
                        Write-Output $VMStatus.HyperVGeneration

                        Write-Verbose "Get-AzVMHyperVGeneration executed succesfully."                        
                    }
                    catch{                                                
                        Write-Error "Error while executing Get-AzVMHyperVGeneration. $_.Exception.Message"                                                
                   }                                      
                }
            
                END {}
}




function Test-AzVMResizeSupportNewSKUGeneration {
<#
.SYNOPSIS
     
 
.DESCRIPTION
 
     
 
.EXAMPLE
     
 
 
.EXAMPLE
     
     
 
.LINK
     
 
.NOTES
    v0.1 - Initial version
 
#>


#Requires -Modules Az.Compute
#Requires -Version 5.1

    [CmdletBinding()]
    param(
        
        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        [string]$ResourceGroupName,
                
        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        [string] $VirtualMachineName,

        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]        
        $VMSKU   
    )

    BEGIN{        
            
    }
    
    PROCESS{
        try{   

            Write-Verbose "Executing Test-AzVMResizeSupportNewSKUGeneration ..."

            Write-Verbose "Checking if new VM SKU '$VMSKU' supports current VM '$VirtualMachineName' Hyper-V Generation ..."

            $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName
            
            $CurrentVMHyperVGeneration = Get-AzVMHyperVGeneration -ResourceGroupName $ResourceGroupName -VirtualMachineName $VirtualMachineName

            $VMSKUGenSupport = Get-AzComputeSKUGenerationSupport -Location $VM.Location -VMSKU $VMSKU

            if ($VMSKUGenSupport.Contains($CurrentVMHyperVGeneration) ) {
               Write-Verbose "New VM SKU '$VMSKU' supports current VM '$VirtualMachineName' Hyper-V Generation '$CurrentVMHyperVGeneration'."
               Write-Output $true
            }else {
                Write-Verbose "New VM SKU '$VMSKU' do not supports current VM '$VirtualMachineName' Hyper-V Generation '$CurrentVMHyperVGeneration'."
                Write-Output $false
            }

            Write-Verbose "Test-AzVMResizeSupportNewSKUGeneration executed succesfully."                        
            
        }
        catch{
            Write-Error "Error while executing Test-AzVMResizeSupportNewSKUGeneration. $_.Exception.Message"                                                            
        }

    }

    END {}
}