AWSTemplateFormatVersion: '2010-09-09'
Description: This main template creates a multi-AZ deployment of a StarWind VSAN
  on AWS in an existing VPC. It deploys 3 StarWind VSAN EC2 instances.
  Two nodes and a witness. **WARNING**
  This template creates EC2 instances and related resources.
  You will be billed for the AWS resources used if you create a stack from this template. (qs-1sgqsectt)
Metadata:
  cfn-lint:
    config:
      ignore_checks:
        - W9006
        - W9901
        - E1029
        - E9101
  QuickStartDocumentation:
    EntrypointName: "Parameters for deploying into an existing VPC"
    Order: "2"
  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: VPC configuration
      Parameters:
      - VPCID
      - PrivateSubnetAID
      - PrivateSubnetBID
      - PrivateSubnetCID
      - RDPSGID
    - Label:
        default: Amazon EC2 configuration
      Parameters:
      - WorkloadInstanceType
      - SWVSANAMIOS
      - SWVSANStorageVolumeSize
      - KeyPairName
      - Node1NetBiosName
      - Node2NetBiosName
      - WitnessNodeNetBiosName
    - Label:
        default: AWS Quick Start configuration
      Parameters:
      - QSS3BucketName
      - QSS3BucketRegion
      - QSS3KeyPrefix
    ParameterLabels:
      KeyPairName:
        default: Key-pair name
      VPCID:
        default: VPC ID
      PrivateSubnetAID:
        default: Node 1 subnet
      PrivateSubnetBID:
        default: Node 2 subnet
      PrivateSubnetCID:
        default: Witness subnet
      RDPSGID:
        default: RDP instances security group ID
      QSS3BucketName:
        default: Quick Start S3 bucket name
      QSS3BucketRegion:
        default: Quick Start S3 bucket Region
      QSS3KeyPrefix:
        default: Quick Start S3 key prefix
      WorkloadInstanceType:
        default: Workload servers instance type
      SWVSANAMIOS:
        default: Workload servers OS version
      SWVSANStorageVolumeSize:
        default: Secondary EBS volume size
      Node1NetBiosName:
        default: Node 1 NetBIOS name
      Node2NetBiosName:
        default: Node 2 NetBIOS name
      WitnessNodeNetBiosName:
        default: Witness node NetBIOS name
Parameters:
  KeyPairName:
    Description: Name of an existing EC2 key pair. The instance will launch with
      this key pair.
    Type: AWS::EC2::KeyPair::KeyName
  VPCID:
    Description: ID of your existing VPC for deployment.
    Type: AWS::EC2::VPC::Id
  PrivateSubnetAID:
    Description: ID of private subnet in Availability Zone 1.
      for the first node (e.g., subnet-a0246dcd).
    Type: AWS::EC2::Subnet::Id
  PrivateSubnetBID:
    Description: ID of private subnet in Availability Zone 2.
      for the second node (e.g., subnet-a0246dcd).
    Type: AWS::EC2::Subnet::Id
  PrivateSubnetCID:
    Description: ID of private subnet in Availability Zone 3.
      for the witness (e.g., subnet-a0246dcd).
    Type: AWS::EC2::Subnet::Id
  RDPSGID:
    Description: ID of the security group for your Remote Desktop instances.
    Type: AWS::EC2::SecurityGroup::Id
  QSS3BucketName:
    AllowedPattern: ^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$
    ConstraintDescription: The Quick Start bucket name can include numbers, lowercase
      letters, uppercase letters, and hyphens (-). It cannot start or end with a 
      hyphen (-).
    Default: aws-quickstart
    Description: Name of the S3 bucket for your copy of the Quick Start assets. 
      Keep the default name unless you are customizing the template. 
      Changing the name updates code references to point to a new Quick 
      Start location. This name can include numbers, lowercase letters, 
      uppercase letters, and hyphens, but do not start or end with a hyphen (-). 
      See https://aws-quickstart.github.io/option1.html.
    Type: String
  QSS3BucketRegion:
    Default: 'us-east-1'
    Description: 'AWS Region where the Quick Start S3 bucket (QSS3BucketName) is 
    hosted. Keep the default Region unless you are customizing the template. 
    Changing this Region updates code references to point to a new Quick Start location. 
    When using your own bucket, specify the Region. 
    See https://aws-quickstart.github.io/option1.html.'
    Type: String
  QSS3KeyPrefix:
    AllowedPattern: ^[0-9a-zA-Z-/]*$
    ConstraintDescription: The Quick Start S3 key prefix can include numbers, lowercase letters,
      uppercase letters, hyphens (-), and forward slashes (/). The prefix should
      end with a forward slash (/).
    Default: quickstart-starwind-vsan/
    Description: S3 key prefix that is used to simulate a folder for your copy of the 
      Quick Start assets. Keep the default prefix unless you are customizing 
      the template. Changing this prefix updates code references to point to 
      a new Quick Start location. This prefix can include numbers, lowercase 
      letters, uppercase letters, hyphens (-), and forward slashes (/). End with 
      a forward slash. See https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html 
      and https://aws-quickstart.github.io/option1.html.
    Type: String
  WorkloadInstanceType:
    AllowedValues:
    - c6i.12xlarge
    - c6i.16xlarge
    - c6i.24xlarge
    - c6i.32xlarge
    - c6i.2xlarge
    - c6i.4xlarge
    - c6i.8xlarge
    - c6i.metal
    - c6id.16xlarge
    - c6id.32xlarge
    - c6id.2xlarge
    - c6id.4xlarge
    - c6id.8xlarge
    - c5.12xlarge
    - c5.18xlarge
    - c5.24xlarge
    - c5.2xlarge
    - c5.4xlarge
    - c5.9xlarge
    - c5.metal
    - c5d.18xlarge
    - c5d.2xlarge
    - c5d.4xlarge
    - c5d.9xlarge
    - c5n.18xlarge
    - c5n.2xlarge
    - c5n.4xlarge
    - c5n.9xlarge
    - c5n.metal
    - d2.2xlarge
    - d2.4xlarge
    - d2.8xlarge
    - f1.16xlarge
    - g2.2xlarge
    - g2.8xlarge
    - g3.16xlarge
    - g3.4xlarge
    - g3.8xlarge
    - g4dn.12xlarge
    - g4dn.16xlarge
    - g4dn.2xlarge
    - g4dn.4xlarge
    - g4dn.8xlarge
    - h1.16xlarge
    - h1.2xlarge
    - h1.4xlarge
    - h1.8xlarge
    - hs1.8xlarge
    - i2.2xlarge
    - i2.4xlarge
    - i2.8xlarge
    - i3.16xlarge
    - i3.2xlarge
    - i3.4xlarge
    - i3.8xlarge
    - i3.metal
    - i3en.12xlarge
    - i3en.24xlarge
    - i3en.2xlarge
    - i3en.3xlarge
    - i3en.6xlarge
    - i3en.large
    - i3en.metal
    - i3en.xlarge
    - m5.12xlarge
    - m5.16xlarge
    - m5.24xlarge
    - m5.2xlarge
    - m5.xlarge
    - m5.4xlarge
    - m5.8xlarge
    - m5.metal
    - m5a.12xlarge
    - m5a.16xlarge
    - m5a.24xlarge
    - m5a.2xlarge
    - m5a.4xlarge
    - m5a.8xlarge
    - m5ad.12xlarge
    - m5ad.24xlarge
    - m5ad.2xlarge
    - m5ad.4xlarge
    - m5d.12xlarge
    - m5d.16xlarge
    - m5d.24xlarge
    - m5d.2xlarge
    - m5d.4xlarge
    - m5d.8xlarge
    - m5d.metal
    - m5dn.12xlarge
    - m5dn.16xlarge
    - m5dn.24xlarge
    - m5dn.2xlarge
    - m5dn.4xlarge
    - m5dn.8xlarge
    - m5n.12xlarge
    - m5n.16xlarge
    - m5n.24xlarge
    - m5n.2xlarge
    - m5n.4xlarge
    - m5n.8xlarge
    - p2.16xlarge
    - p2.8xlarge
    - p3.16xlarge
    - p3.2xlarge
    - p3.8xlarge
    - p3dn.24xlarge
    - r5.12xlarge
    - r5.16xlarge
    - r5.24xlarge
    - r5.2xlarge
    - r5.4xlarge
    - r5.8xlarge
    - r5.metal
    - r5a.12xlarge
    - r5a.16xlarge
    - r5a.24xlarge
    - r5a.2xlarge
    - r5a.4xlarge
    - r5a.8xlarge
    - r5ad.12xlarge
    - r5ad.24xlarge
    - r5ad.2xlarge
    - r5ad.4xlarge
    - r5d.12xlarge
    - r5d.16xlarge
    - r5d.24xlarge
    - r5d.2xlarge
    - r5d.4xlarge
    - r5d.8xlarge
    - r5d.metal
    - r5dn.12xlarge
    - r5dn.16xlarge
    - r5dn.24xlarge
    - r5dn.2xlarge
    - r5dn.4xlarge
    - r5dn.8xlarge
    - r5n.12xlarge
    - r5n.16xlarge
    - r5n.24xlarge
    - r5n.2xlarge
    - r5n.4xlarge
    - r5n.8xlarge
    - t3.2xlarge
    - t3a.2xlarge
    - x1.16xlarge
    - x1.32xlarge
    - x1e.16xlarge
    - x1e.2xlarge
    - x1e.32xlarge
    - x1e.4xlarge
    - x1e.8xlarge
    - z1d.12xlarge
    - z1d.2xlarge
    - z1d.3xlarge
    - z1d.6xlarge
    - z1d.metal
    ConstraintDescription: Must contain a valid instance type.
    Default: m5.xlarge
    Description: Type of EC2 instance for the workload instances.
    Type: String
  SWVSANAMIOS:
    Type: String
    Description: Operating system version of the StarWind instances to be created.
    Default: WindowsServer2019
    AllowedValues:
      - WindowsServer2019
      - WindowsServer2016
  SWVSANStorageVolumeSize:
    Type: Number
    Default: 100
    ConstraintDescription: Must be between 1 GB and 16,000 GB (16 TB).
    MinValue: 1
    MaxValue: 16000
    Description: Size (in GB) for StarWind VSAN virtualized storage.
  Node1NetBiosName:
    AllowedPattern: '[a-zA-Z0-9\-]+'
    Default: "SWVSANNODE1"
    Description: "NetBIOS name of storage node 1 (up to 15 characters)."
    MaxLength: '15'
    MinLength: '1'
    Type: "String"
  Node2NetBiosName:
    AllowedPattern: '[a-zA-Z0-9\-]+'
    Default: "SWVSANNODE2"
    Description: "NetBIOS name of storage node 2 (up to 15 characters)."
    MaxLength: '15'
    MinLength: '1'
    Type: "String"
  WitnessNodeNetBiosName:
    AllowedPattern: '[a-zA-Z0-9\-]+'
    Default: "SWVSANWIT"
    Description: "NetBIOS name of witness node (up to 15 characters)."
    MaxLength: '15'
    MinLength: '1'
    Type: "String"
Conditions:
  UsingDefaultBucket: !Equals [!Ref QSS3BucketName, 'aws-quickstart']
Resources:
  SSMWaitHandle:
    Type: AWS::CloudFormation::WaitConditionHandle
  SSMWaitCondition:
    Type: AWS::CloudFormation::WaitCondition
    CreationPolicy:
      ResourceSignal:
        Timeout: PT20M
        Count: 1
    DependsOn: "StorageNode1Stack"
    Properties:
      Handle: !Ref SSMWaitHandle
      Timeout: "1200"
      Count: 1
  SWVSANQuickstartSSMAutomation:
    Type: AWS::SSM::Document
    Properties:
      DocumentType: Automation
      Content:
        description: ''
        schemaVersion: '0.3'
        assumeRole: '{{AutomationAssumeRole}}'
        parameters:
          StackName:
            type: String
            description: The CloudFormation stack name this automation belongs to
          Node1Name:
            type: String
            description: NetBIOS name for storage node 1
          Node2Name:
            type: String
            description: NetBIOS name for storage node 2
          WitnessNodeName:
            type: String
            description: NetBIOS name for witness node
          AutomationAssumeRole:
            type: String
            description: |-
              (Optional) The ARN of the role that allows automation to perform
                    the actions on your behalf
        mainSteps:
          - name: "swvsanNode1InstanceId"
            action: aws:executeAwsApi
            onFailure: "step:signalfailure"
            nextStep: swvsanNode2InstanceId
            inputs:
              Service: ec2
              Api: DescribeInstances
              Filters:
              - Name: "tag:Name"
                Values: ["StarWind Node 1"]
              - Name: "tag:qs-sw-vsan:parent-stack-name"
                Values: ["{{StackName}}"]
            outputs:
            - Name: InstanceId
              Selector: "$.Reservations[0].Instances[0].InstanceId"
              Type: String
          - name: "swvsanNode2InstanceId"
            action: aws:executeAwsApi
            onFailure: "step:signalfailure"
            nextStep: swvsanWitnessInstanceId
            inputs:
              Service: ec2
              Api: DescribeInstances
              Filters:
              - Name: "tag:Name"
                Values: ["StarWind Node 2"]
              - Name: "tag:qs-sw-vsan:parent-stack-name"
                Values: ["{{StackName}}"]
            outputs:
            - Name: InstanceId
              Selector: "$.Reservations[0].Instances[0].InstanceId"
              Type: String
          - name: "swvsanWitnessInstanceId"
            action: aws:executeAwsApi
            onFailure: "step:signalfailure"
            nextStep: WaitForNode1
            inputs:
              Service: ec2
              Api: DescribeInstances
              Filters:
              - Name: "tag:Name"
                Values: ["StarWind Witness"]
              - Name: "tag:qs-sw-vsan:parent-stack-name"
                Values: ["{{StackName}}"]
            outputs:
            - Name: InstanceId
              Selector: "$.Reservations[0].Instances[0].InstanceId"
              Type: String
          - name: WaitForNode1
            action: 'aws:waitForAwsResourceProperty'
            nextStep: WaitForNode2
            inputs:
              Service: ec2
              Api: DescribeInstanceStatus
              PropertySelector: '$.InstanceStatuses[0].InstanceState.Name'
              DesiredValues:
                - running
              InstanceIds:
                - '{{swvsanNode1InstanceId.InstanceId}}'
            timeoutSeconds: 60
          - name: WaitForNode2
            action: 'aws:waitForAwsResourceProperty'
            nextStep: WaitForWitness
            inputs:
              Service: ec2
              Api: DescribeInstanceStatus
              PropertySelector: '$.InstanceStatuses[0].InstanceState.Name'
              DesiredValues:
                - running
              InstanceIds:
                - '{{swvsanNode2InstanceId.InstanceId}}'
            timeoutSeconds: 60
          - name: WaitForWitness
            action: 'aws:waitForAwsResourceProperty'
            nextStep: renameComputer
            inputs:
              Service: ec2
              Api: DescribeInstanceStatus
              PropertySelector: '$.InstanceStatuses[0].InstanceState.Name'
              DesiredValues:
                - running
              InstanceIds:
                - '{{swvsanWitnessInstanceId.InstanceId}}'
            timeoutSeconds: 60
          - name: renameComputer
            action: aws:runCommand
            nextStep: EnableIscsiMpio
            onFailure: "step:signalfailure"
            inputs:
              DocumentName: AWS-RunPowerShellScript
              InstanceIds:
                - "{{swvsanNode1InstanceId.InstanceId}}"
                - "{{swvsanNode2InstanceId.InstanceId}}"
                - "{{swvsanWitnessInstanceId.InstanceId}}"
              # CloudWatchOutputConfig:
              #   CloudWatchOutputEnabled: "true"
              #   CloudWatchLogGroupName: !Sub '/aws/Quick_Start/${AWS::StackName}'
              Parameters:
                commands:
                  - |
                    $currentNodeInstanceId = Invoke-WebRequest -Uri http://169.254.169.254/latest/meta-data/instance-id -UseBasicParsing | Select -expand "Content"
                    $currentNodeName = Get-EC2Tag -Filter @{Name="resource-type";Values="instance"},@{Name="resource-id";Values="$currentNodeInstanceId"},@{Name="key";Values="Name"} -Select 'Tags.Value'
                    $newName = switch ( $currentNodeName )
                    {
                        'StarWind Node 1'   { '{{Node1Name}}'       }
                        'StarWind Node 2'   { '{{Node2Name}}'       }
                        'StarWind Witness'  { '{{WitnessNodeName}}' }
                    }
                    # get current computer name
                    $currentName = (gwmi WIN32_ComputerSystem).Name
                    # if name is not updated
                    if ($currentName -ne $newName) {
                      # change computer name
                      Rename-Computer -NewName $newName
                      # Tell SSM to reboot.
                      exit 3010
                    }
                    echo "New name is: $currentName"
          - name: EnableIscsiMpio
            action: aws:runCommand
            nextStep: StaticIPsConfig
            onFailure: "step:signalfailure"
            inputs:
              DocumentName: AWS-RunPowerShellScript
              InstanceIds:
                - "{{swvsanNode1InstanceId.InstanceId}}"
                - "{{swvsanNode2InstanceId.InstanceId}}"
                # - "{{swvsanWitnessInstanceId.InstanceId}}"
              # CloudWatchOutputConfig:
              #   CloudWatchOutputEnabled: "true"
              #   CloudWatchLogGroupName: !Sub '/aws/Quick_Start/${AWS::StackName}'
              Parameters:
                commands:
                  - |
                    # Get iscsi service info
                    $iscsi = Get-Service MSiSCSI
                    # if startup type is not automatic
                    if ($iscsi.StartType -ne "Automatic") {
                      # Set start type to automatic
                      $iscsi | Set-Service -StartType Automatic
                      # start the service (incase it is stopped)
                      Start-Service MSiSCSI
                    }

                    # Get MPIO feature info
                    $mpio = Get-WindowsFeature -Name Multipath-IO
                    # if its not installed
                    if (-not $mpio.Installed) {
                      # install the MPIO feature
                      Install-WindowsFeature -Name Multipath-IO
                      # Tell SSM to reboot.
                      exit 3010
                    }

                    # enable MPIO on iSCSI
                    Enable-MSDSMAutomaticClaim -BusType "iSCSI"
          - name: StaticIPsConfig
            action: aws:runCommand
            nextStep: StarWindEBSVolumeConfig
            onFailure: "step:signalfailure"
            inputs:
              DocumentName: AWS-RunPowerShellScript
              InstanceIds:
                - "{{swvsanNode1InstanceId.InstanceId}}"
                - "{{swvsanNode2InstanceId.InstanceId}}"
                # - "{{swvsanWitnessInstanceId.InstanceId}}"
              # CloudWatchOutputConfig:
              #   CloudWatchOutputEnabled: "true"
              #   CloudWatchLogGroupName: !Sub '/aws/Quick_Start/${AWS::StackName}'
              Parameters:
                commands:
                  - |
                    # Disable windows server firewall to allow inter-node communication
                    Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False

                    $currentNodeInstanceId = Invoke-WebRequest -Uri http://169.254.169.254/latest/meta-data/instance-id -UseBasicParsing | Select -expand "Content"
                    $ENInames = "Management/Witness ENI","iSCSI ENI","Syncronization ENI"
                    # storing commands in an array as changing network config on the fly causes IAM errors
                    $setupCommands = new-object collections.generic.list[object]

                    foreach ($eni in $ENInames) {
                      $eniIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$currentNodeInstanceId"},@{Name="description";Values="$eni"} -Select 'NetworkInterfaces.PrivateIpAddress'
                      $ethernetDeviceName = (Get-NetIPAddress "$eniIp").InterfaceAlias
                      $networkInterfaceIndex = (Get-NetIPAddress "$eniIp").InterfaceIndex
                      $dnsIp = (Get-NetIPConfiguration -InterfaceIndex (Get-NetIPAddress "$eniIp").InterfaceIndex).DNSServer.ServerAddresses
                      $defaultGateway = (Get-NetIPConfiguration -InterfaceIndex (Get-NetIPAddress "$eniIp").InterfaceIndex).IPv4DefaultGateway.NextHop
                      $subnetMask = (Get-WmiObject Win32_NetworkAdapterConfiguration | Where IPAddress -eq "$eniIp").IPSubnet[0]

                      $eni
                      $eniIp
                      $ethernetDeviceName
                      $networkInterfaceIndex
                      $dnsIp
                      $defaultGateway
                      $subnetMask

                      if ($eni -eq "Management/Witness ENI") {
                        # For Management I need to add Gateway
                        $setupCommands.Add("netsh interface ipv4 set address name='$ethernetDeviceName' static $eniIp $subnetMask $defaultGateway")
                      }
                      else
                      {
                        # For iSCSI and Sync (no default gateway)
                        $setupCommands.Add("netsh interface ipv4 set address name='$ethernetDeviceName' static $eniIp $subnetMask")
                      }

                      # # Setup DNS Server
                      $setupCommands.Add("Set-DnsClientServerAddress -InterfaceIndex $networkInterfaceIndex -ServerAddresses ('$dnsIp')")

                      if ($eni -eq "Syncronization ENI") {
                        $currentNodeName = Get-EC2Tag -Filter @{Name="resource-type";Values="instance"},@{Name="resource-id";Values="$currentNodeInstanceId"},@{Name="key";Values="Name"} -Select 'Tags.Value'
                        $otherNodeName   = "StarWind Node 2"
                        if ($currentNodeName -eq $otherNodeName) {
                          $otherNodeName = "StarWind Node 1"
                        }

                        $otherNodeInstanceId = Get-EC2Instance -Filter @{Name="tag:qs-sw-vsan:parent-stack-name";Values="{{StackName}}"},@{Name="tag:Name";Values="$otherNodeName"} -Select 'Reservations.Instances.InstanceId'
                        # Get other node Syncronization ENI IP
                        $otherNodeSyncENIIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$otherNodeInstanceId"},@{Name="description";Values="Syncronization ENI"} -Select 'NetworkInterfaces.PrivateIpAddress'

                        # for primary and secondary storage nodes to talk to each other over Sync ENI
                        $setupCommands.Add("ROUTE -p ADD $otherNodeSyncENIIp MASK 255.255.255.255 $defaultGateway METRIC 15 IF $networkInterfaceIndex")
                      }
                      elseif ($eni -eq "iSCSI ENI") {
                        # Route for ISCSI ENI to talk to clients with low Metric (so it is only used when ENI/IP is specified as source)
                        $setupCommands.Add("ROUTE -p ADD 0.0.0.0 MASK 0.0.0.0 $defaultGateway METRIC 100 IF $networkInterfaceIndex")
                      }
                    }

                    foreach ($command in $setupCommands) {
                      Invoke-Expression $command
                    }
          - name: StarWindEBSVolumeConfig
            action: aws:runCommand
            nextStep: CreateStarWindQuickStartDirectoryOnWitness
            onFailure: "step:signalfailure"
            inputs:
              DocumentName: AWS-RunPowerShellScript
              InstanceIds:
                - "{{swvsanNode1InstanceId.InstanceId}}"
                - "{{swvsanNode2InstanceId.InstanceId}}"
              # CloudWatchOutputConfig:
              #   CloudWatchOutputEnabled: "true"
              #   CloudWatchLogGroupName: !Sub '/aws/Quick_Start/${AWS::StackName}'
              Parameters:
                commands:
                  - |
                    # Initialze disk (Secondary EBS volume)
                    Initialize-Disk -Number 1 -PartitionStyle MBR
                    # Create partition using all space as D drive
                    New-Partition –DiskNumber 1 -DriveLetter D –UseMaximumSize
                    Format-Volume -DriveLetter D -FileSystem NTFS -Confirm:$false
                    # Create folder in D drive to store the StarWind HA device image
                    New-Item -ItemType Directory -Path D:\StarWind-QuickStart

          - name: CreateStarWindQuickStartDirectoryOnWitness
            action: aws:runCommand
            nextStep: StarWindVSANConfig
            onFailure: "step:signalfailure"
            inputs:
              DocumentName: AWS-RunPowerShellScript
              InstanceIds:
                - "{{swvsanWitnessInstanceId.InstanceId}}"
              # CloudWatchOutputConfig:
              #   CloudWatchOutputEnabled: "true"
              #   CloudWatchLogGroupName: !Sub '/aws/Quick_Start/${AWS::StackName}'
              Parameters:
                commands:
                  - |
                    New-Item -ItemType Directory -Path C:\StarWind-QuickStart
          - name: StarWindVSANConfig
            action: aws:runCommand
            nextStep: ConnectIscsiStarWindDevices
            onFailure: "step:signalfailure"
            inputs:
              DocumentName: AWS-RunPowerShellScript
              InstanceIds:
                - "{{swvsanNode1InstanceId.InstanceId}}"
              # CloudWatchOutputConfig:
              #   CloudWatchOutputEnabled: "true"
              #   CloudWatchLogGroupName: !Sub '/aws/Quick_Start/${AWS::StackName}'
              Parameters:
                commands:
                  - |

                    $currentNodeInstanceId = Invoke-WebRequest -Uri http://169.254.169.254/latest/meta-data/instance-id -UseBasicParsing | Select -expand "Content"

                    $otherNodeName   = "StarWind Node 2"
                    $otherNodeInstanceId = Get-EC2Instance -Filter @{Name="tag:qs-sw-vsan:parent-stack-name";Values="{{StackName}}"},@{Name="tag:Name";Values="$otherNodeName"} -Select 'Reservations.Instances.InstanceId'
                    $WitnessNodeName   = "StarWind Witness"
                    $witnessNodeInstanceId = Get-EC2Instance -Filter @{Name="tag:qs-sw-vsan:parent-stack-name";Values="{{StackName}}"},@{Name="tag:Name";Values="$WitnessNodeName"} -Select 'Reservations.Instances.InstanceId'

                    # Get node 1 Syncronization ENI IP
                    $currentNodeSyncENIIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$currentNodeInstanceId"},@{Name="description";Values="Syncronization ENI"} -Select 'NetworkInterfaces.PrivateIpAddress'
                    # Get node 1 Management ENI IP
                    $currentNodeManagementENIIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$currentNodeInstanceId"},@{Name="description";Values="Management/Witness ENI"} -Select 'NetworkInterfaces.PrivateIpAddress'
                    # Get node 2 Syncronization ENI IP
                    $otherNodeSyncENIIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$otherNodeInstanceId"},@{Name="description";Values="Syncronization ENI"} -Select 'NetworkInterfaces.PrivateIpAddress'
                    # Get node 2 Management ENI IP
                    $otherNodeManagementENIIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$otherNodeInstanceId"},@{Name="description";Values="Management/Witness ENI"} -Select 'NetworkInterfaces.PrivateIpAddress'

                    # Get witness node Management ENI IP
                    $witnessNodeManagementENIIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$witnessNodeInstanceId"},@{Name="description";Values="Management/Witness ENI"} -Select 'NetworkInterfaces.PrivateIpAddress'

                    $first_node_csv   = "iqn.2008-08.com.starwindsoftware:" + "{{Node1Name}}".ToLower() + "-csv"
                    $second_node_csv  = "iqn.2008-08.com.starwindsoftware:" + "{{Node2Name}}".ToLower() + "-csv"
                    $witness_node_csv = "iqn.2008-08.com.starwindsoftware:" + "{{WitnessNodeName}}".ToLower() + "-csv"
                    &"C:\Program Files\StarWind Software\StarWind\StarWindX\Samples\powershell\Create_HA_Node_Majority.ps1" -addr $currentNodeManagementENIIp -addr2 $otherNodeManagementENIIp -addrW $witnessNodeManagementENIIp -syncInterface "#p2=${otherNodeSyncENIIp}:3260;#p3=${witnessNodeManagementENIIp}:3260" -syncInterface2 "#p1=${currentNodeSyncENIIp}:3260;#p3=${witnessNodeManagementENIIp}:3260" -syncInterfaceW "#p1=${currentNodeManagementENIIp}:3260;#p2=${otherNodeManagementENIIp}:3260" -imagePath "D:\StarWind-QuickStart" -imagePath2 "D:\StarWind-QuickStart" -imagePathW "C:\StarWind-QuickStart" -targetAlias "csv" -targetAlias2 "csv" -targetAliasW "csv" -targetName $first_node_csv -targetName2 $second_node_csv -targetNameW $witness_node_csv -cacheMode $null

                    $freeDiskSpace =  Get-PSDrive D | Select-Object @{Expression={$_.Free/1MB}} | Select -expand '$_.Free/1MB'
                    # Extend the HA device to the full size of the volume
                    &"C:\Program Files\StarWind Software\StarWind\StarWindX\Samples\powershell\ExtendDevice.ps1" -addr $currentNodeSyncENIIp -deviceName "HAImage1" -extendSize $freeDiskSpace

          - name: ConnectIscsiStarWindDevices
            action: aws:runCommand
            nextStep: CreateVolumeOnIscsiStarWindDevices
            onFailure: "step:signalfailure"
            inputs:
              DocumentName: AWS-RunPowerShellScript
              InstanceIds:
                - "{{swvsanNode1InstanceId.InstanceId}}"
                - "{{swvsanNode2InstanceId.InstanceId}}"
              # CloudWatchOutputConfig:
              #   CloudWatchOutputEnabled: "true"
              #   CloudWatchLogGroupName: !Sub '/aws/Quick_Start/${AWS::StackName}'
              Parameters:
                commands:
                  - |
                    $first_node_csv   = "iqn.2008-08.com.starwindsoftware:" + "{{Node1Name}}".ToLower() + "-csv"
                    $second_node_csv  = "iqn.2008-08.com.starwindsoftware:" + "{{Node2Name}}".ToLower() + "-csv"

                    $node1InstanceId = Get-EC2Instance -Filter @{Name="tag:qs-sw-vsan:parent-stack-name";Values="{{StackName}}"},@{Name="tag:Name";Values="StarWind Node 1"} -Select 'Reservations.Instances.InstanceId'
                    $node2InstanceId = Get-EC2Instance -Filter @{Name="tag:qs-sw-vsan:parent-stack-name";Values="{{StackName}}"},@{Name="tag:Name";Values="StarWind Node 2"} -Select 'Reservations.Instances.InstanceId'

                    $node1IscsiIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$node1InstanceId"},@{Name="description";Values="iSCSI ENI"} -Select 'NetworkInterfaces.PrivateIpAddress'
                    $node2IscsiIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$node2InstanceId"},@{Name="description";Values="iSCSI ENI"} -Select 'NetworkInterfaces.PrivateIpAddress'

                    $currentNodeInstanceId = Invoke-WebRequest -Uri http://169.254.169.254/latest/meta-data/instance-id -UseBasicParsing | Select -expand "Content"

                    if ($currentNodeInstanceId -eq $node1InstanceId) {
                      $sourceIscsiIP      = $node1IscsiIp
                      $destinationIscsiIp = $node2IscsiIp
                      $currentNodeIscsiAddress  = $first_node_csv
                      $otherNodeIscsiAddress    = $second_node_csv
                    }
                    elseif ($currentNodeInstanceId -eq $node2InstanceId)
                    {
                      $sourceIscsiIP      = $node2IscsiIp
                      $destinationIscsiIp = $node1IscsiIp
                      $currentNodeIscsiAddress  = $second_node_csv
                      $otherNodeIscsiAddress    = $first_node_csv
                    }

                    New-IscsiTargetPortal -TargetPortalAddress 127.0.0.1 -TargetPortalPortNumber 3260
                    New-IscsiTargetPortal -TargetPortalAddress $destinationIscsiIp  -TargetPortalPortNumber 3260 -InitiatorPortalAddress $sourceIscsiIP

                    Connect-IscsiTarget -NodeAddress $currentNodeIscsiAddress -TargetPortalAddress 127.0.0.1 -TargetPortalPortNumber 3260 -IsMultipathEnabled $true -IsPersistent $true
                    Connect-IscsiTarget -NodeAddress $otherNodeIscsiAddress -TargetPortalAddress $destinationIscsiIp -InitiatorPortalAddress $sourceIscsiIP -TargetPortalPortNumber 3260 -IsMultipathEnabled $true -IsPersistent $true

                    Set-MSDSMGlobalDefaultLoadBalancePolicy -Policy LQD

                    # print currently configured iscsi targets
                    Get-IscsiTarget
          - name: CreateVolumeOnIscsiStarWindDevices
            action: aws:runCommand
            nextStep: renameStorageNode1EC2
            onFailure: "step:signalfailure"
            inputs:
              DocumentName: AWS-RunPowerShellScript
              InstanceIds:
                - "{{swvsanNode1InstanceId.InstanceId}}"
              # CloudWatchOutputConfig:
              #   CloudWatchOutputEnabled: "true"
              #   CloudWatchLogGroupName: !Sub '/aws/Quick_Start/${AWS::StackName}'
              Parameters:
                commands:
                  - |
                    $device2ID = Get-Content D:\StarWind-QuickStart\HAimage2.swdsk | Select-Object -Index 7

                    $device2ID = $device2ID -replace "<serial_id>", "" -replace "</serial_id>", ""

                    $device2ID = $device2ID.Trim()

                    $disk2 = Get-Disk | Where-Object SerialNumber -eq $device2ID | Initialize-Disk -PartitionStyle GPT

                    $diskselection2 = Get-Disk | Where-Object SerialNumber -eq $device2ID | foreach Number

                    New-Volume -FriendlyName CSV -DiskNumber $diskselection2 -FileSystem NTFS -AllocationUnitSize 4096
          - name: renameStorageNode1EC2
            action: 'aws:executeAwsApi'
            nextStep: renameStorageNode2EC2
            inputs:
              Service: ec2
              Api: CreateTags
              Resources:
                - '{{swvsanNode1InstanceId.InstanceId}}'
              Tags:
                - Key: Name
                  Value: '{{Node1Name}}'
          - name: renameStorageNode2EC2
            action: 'aws:executeAwsApi'
            nextStep: renameWitnessNodeEC2
            inputs:
              Service: ec2
              Api: CreateTags
              Resources:
                - '{{swvsanNode2InstanceId.InstanceId}}'
              Tags:
                - Key: Name
                  Value: '{{Node2Name}}'
          - name: renameWitnessNodeEC2
            action: 'aws:executeAwsApi'
            nextStep: signalsuccess
            inputs:
              Service: ec2
              Api: CreateTags
              Resources:
                - '{{swvsanWitnessInstanceId.InstanceId}}'
              Tags:
                - Key: Name
                  Value: '{{WitnessNodeName}}'
            # If all steps complete successfully signals CFN of Success
          - name: "signalsuccess"
            action: "aws:executeAwsApi"
            isEnd: True
            inputs:
              Service: cloudformation
              Api: SignalResource
              LogicalResourceId: "SSMWaitCondition"
              StackName: "{{StackName}}"
              Status: SUCCESS
              UniqueId: "{{swvsanNode1InstanceId.InstanceId}}"
           # If any steps fails signals CFN of Failure
          - name: "signalfailure"
            action: "aws:executeAwsApi"
            inputs:
              Service: cloudformation
              Api: SignalResource
              LogicalResourceId: "SSMWaitCondition"
              StackName: "{{StackName}}"
              Status: FAILURE
              UniqueId: "{{swvsanNode1InstanceId.InstanceId}}"
  SWVSANSSMIAMRole:
    Type: AWS::IAM::Role
    Metadata:
      cfn-lint:
        config:
          ignore_checks:
            - EIAMPolicyWildcardResource
          ignore_reason:
            - "Scope is limited appropriately"
    Properties:
      Policies:
        - PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Action:
                  - s3:GetObject
                Resource:
                  - !Sub 'arn:${AWS::Partition}:s3:::aws-ssm-${AWS::Region}/*'
                  - !Sub 'arn:${AWS::Partition}:s3:::aws-windows-downloads-${AWS::Region}/*'
                  - !Sub 'arn:${AWS::Partition}:s3:::amazon-ssm-${AWS::Region}/*'
                  - !Sub 'arn:${AWS::Partition}:s3:::amazon-ssm-packages-${AWS::Region}/*'
                  - !Sub 'arn:${AWS::Partition}:s3:::${AWS::Region}-birdwatcher-prod/*'
                  - !Sub 'arn:${AWS::Partition}:s3:::patch-baseline-snapshot-${AWS::Region}/*'
                Effect: Allow
          PolicyName: ssm-custom-s3-policy
        - PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - cloudformation:SignalResource
                Resource: !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*'
              - Effect: Allow
                Action:
                  - ec2:DescribeInstances
                  - ec2:DescribeInstanceStatus
                  - ec2:CreateTags
                  - ssm:DescribeInstanceInformation
                  - ssm:ListCommands
                  - ssm:ListCommandInvocations
                  - ssm:SendCommand
                  - ssm:CancelCommand
                Resource: '*'
          PolicyName: QS-SWVSAN-SSM-AutomationExecution
      Path: /
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - sts:AssumeRole
            Principal:
              Service:
                - ec2.amazonaws.com
                - ssm.amazonaws.com
            Effect: Allow
        Version: '2012-10-17'
  SWVSANEC2IAMRole:
    Type: AWS::IAM::Role
    Properties:
      Policies:
        - PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Action:
                  - s3:GetObject
                Resource:
                  - !Sub 'arn:${AWS::Partition}:s3:::aws-ssm-${AWS::Region}/*'
                  - !Sub 'arn:${AWS::Partition}:s3:::aws-windows-downloads-${AWS::Region}/*'
                  - !Sub 'arn:${AWS::Partition}:s3:::amazon-ssm-${AWS::Region}/*'
                  - !Sub 'arn:${AWS::Partition}:s3:::amazon-ssm-packages-${AWS::Region}/*'
                  - !Sub 'arn:${AWS::Partition}:s3:::${AWS::Region}-birdwatcher-prod/*'
                  - !Sub 'arn:${AWS::Partition}:s3:::patch-baseline-snapshot-${AWS::Region}/*'
                Effect: Allow
          PolicyName: ssm-custom-s3-policy
        - PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ssm:StartAutomationExecution
                Resource: !Sub 'arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/${SWVSANQuickstartSSMAutomation}:$DEFAULT'
          PolicyName: QS-SWVSAN-SSM-Trigger
        - PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - iam:PassRole
                Resource: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${SWVSANSSMIAMRole}'
          PolicyName: QS-SWVSAN-SSM-PassRole
      Path: /
      ManagedPolicyArns:
        - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore'
        - !Sub 'arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy'
        - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonEC2ReadOnlyAccess'
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - sts:AssumeRole
            Principal:
              Service:
                - ec2.amazonaws.com
                - ssm.amazonaws.com
            Effect: Allow
        Version: '2012-10-17'
  SWVSANEC2IAMInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref 'SWVSANEC2IAMRole'
      Path: /
  SecurityGroupsStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL:
        !Sub
          - 'https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/swvsan-securitygroups.template.yaml'
          - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion]
            S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName]
      Parameters:
        VPCID: !Ref VPCID
        ParentStackName: !Sub ${AWS::StackName}
        RDPSGID: !Ref RDPSGID
  StorageNode1Stack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL:
        !Sub
          - 'https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/swvsan-instance.template.yaml'
          - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion]
            S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName]
      Parameters:
        WorkloadInstanceType: !Ref WorkloadInstanceType
        SWVSANAMIOS: !Ref SWVSANAMIOS
        KeyPairName: !Ref KeyPairName
        PrivateSubnetID: !Ref PrivateSubnetAID
        ISCSIENISGId: !GetAtt SecurityGroupsStack.Outputs.ISCSIENISGId
        ManagementENISGId: !GetAtt SecurityGroupsStack.Outputs.ManagementENISGId
        SyncronizationENISGId: !GetAtt SecurityGroupsStack.Outputs.SyncronizationENISGId
        SWVSANStorageVolumeSize: !Ref SWVSANStorageVolumeSize
        SWVSANEC2InstanceName: 'StarWind Node 1'
        SWVSANEC2IAMInstanceProfile: !Ref SWVSANEC2IAMInstanceProfile
        SWVSANEC2IAMInstanceUserData: !Base64
          Fn::Join:
            - ''
            - - "<powershell>\n"
              - 'Start-SSMAutomationExecution -DocumentName '
              - !Sub '"${SWVSANQuickstartSSMAutomation}"'
              - ' -Parameter @{"StackName"='
              - !Sub '"${AWS::StackName}"'
              - ';"Node1Name"='
              - !Sub '"${Node1NetBiosName}"'
              - ';"Node2Name"='
              - !Sub '"${Node2NetBiosName}"'
              - ';"WitnessNodeName"='
              - !Sub '"${WitnessNodeNetBiosName}"'
              - ';"AutomationAssumeRole"='
              - !Sub '"arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${SWVSANSSMIAMRole}"'
              - '}'
              - "\n"
              - "</powershell>\n"
        ParentStackName: !Sub ${AWS::StackName}
  StorageNode2Stack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL:
        !Sub
          - 'https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/swvsan-instance.template.yaml'
          - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion]
            S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName]
      Parameters:
        WorkloadInstanceType: !Ref WorkloadInstanceType
        SWVSANAMIOS: !Ref SWVSANAMIOS
        KeyPairName: !Ref KeyPairName
        PrivateSubnetID: !Ref PrivateSubnetBID
        ISCSIENISGId: !GetAtt SecurityGroupsStack.Outputs.ISCSIENISGId
        ManagementENISGId: !GetAtt SecurityGroupsStack.Outputs.ManagementENISGId
        SyncronizationENISGId: !GetAtt SecurityGroupsStack.Outputs.SyncronizationENISGId
        SWVSANStorageVolumeSize: !Ref SWVSANStorageVolumeSize
        SWVSANEC2InstanceName: 'StarWind Node 2'
        SWVSANEC2IAMInstanceProfile: !Ref SWVSANEC2IAMInstanceProfile
        ParentStackName: !Sub ${AWS::StackName}
  WitnessNodeStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL:
        !Sub
          - 'https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/swvsan-instance.template.yaml'
          - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion]
            S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName]
      Parameters:
        NodeOrWitness: Witness
        WorkloadInstanceType: !Ref WorkloadInstanceType
        SWVSANAMIOS: !Ref SWVSANAMIOS
        KeyPairName: !Ref KeyPairName
        PrivateSubnetID: !Ref PrivateSubnetCID
        ManagementENISGId: !GetAtt SecurityGroupsStack.Outputs.ManagementENISGId
        SWVSANStorageVolumeSize: !Ref SWVSANStorageVolumeSize
        SWVSANEC2InstanceName: 'StarWind Witness'
        SWVSANEC2IAMInstanceProfile: !Ref SWVSANEC2IAMInstanceProfile
        ParentStackName: !Sub ${AWS::StackName}
Outputs:
  StorageNode1ManagementIP:
    Description: Management IP address of storage node 1
    Value: !GetAtt StorageNode1Stack.Outputs.SWVSANEC2InstanceManagementIP
  StorageNode1IscsiIP:
    Description: iSCSI IP address of storage node 1
    Value: !GetAtt StorageNode1Stack.Outputs.SWVSANEC2InstanceIscsiIP
  StorageNode1SyncronizationIP:
    Description: Synchronization IP address of storage node 1
    Value: !GetAtt StorageNode1Stack.Outputs.SWVSANEC2InstanceSyncronizationIP
  StorageNode2ManagementIP:
    Description: Management IP address of storage node 2
    Value: !GetAtt StorageNode2Stack.Outputs.SWVSANEC2InstanceManagementIP
  StorageNode2IscsiIP:
    Description: iSCSI IP address of storage node 2
    Value: !GetAtt StorageNode2Stack.Outputs.SWVSANEC2InstanceIscsiIP
  StorageNode2SyncronizationIP:
    Description: Synchronization IP address of storage node 2
    Value: !GetAtt StorageNode2Stack.Outputs.SWVSANEC2InstanceSyncronizationIP
  WitnessNodeManagementIP:
    Description: Management IP address of witness node
    Value: !GetAtt WitnessNodeStack.Outputs.SWVSANEC2InstanceManagementIP