1

Trying to figure out why 3 powershell cmdlets are not working when using AWS SM Automation inputs Script, but they do work when running them in a local script directly on my Windows instances.

The cmdlets: get-ciminstance and rename-computer work in Powershell 7, but add-computer requires importing the module Microsoft.PowerShell.Management for using the cmdlet Add-Computer in PS7, which does work in my automation document execution. However, the Import-Module CimCmdlets does not work in aws ssm automation execution, I get the error: Could not load file or assembly 'Microsoft.Management.Infrastructure.CimCmdlets, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.The 'Get-CimInstance' command was found in the module 'CimCmdlets', but the module could not be loaded. And if importing the required module for add-computer cmdlet works, why do I get an error for this cmdlet still? Very confused.

And the error for the 2 computer cmdlets is this:

The term 'Rename-Computer' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.The term 'Add-Computer' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again`

Note that the Import-Module CimCmdlets works when running it locally when I am in the Windows instance powerhshell terminal directly.

I also noticed that the cmdlets for Rename and Add computer have the disclaimer | This cmdlet is only available on the Windows platform. Could it be that because I am using AWS SSM Automation to execute the script on my Windows instances that the "platform" is not Windows?

Just seeking any insight on this. I have read that it could be related the reference dll of Infrastructure file?

This is my systems manager automation document with the ps1 script contents inserted:

description: |-


  ---
  # Automation to domain join Windows EC2 instance

schemaVersion: '0.3'
assumeRole: 'arn:***REDACTED***:role/AutomationServiceRole'
parameters:
  secretParam:
    type: String
  hostnameParam:
    type: String
  InstanceId:
    type: 'AWS::EC2::Instance::Id'
    default: i-0983ac4bfa3d22189
mainSteps:
  - name: domainjoin
    action: 'aws:executeScript'
    inputs:
      Runtime: PowerShell 7.0
      InputPayload:
        host_name: '{{hostnameParam}}'
        secret_set: '{{secretParam}}'
      Script: |-
        # Install-Module AWSPowerShell -Force
        # Import-Module AWSPowerShell
        
        Install-Module -Name AWS.Tools.Installer -Force
        # Install-Module AWS.Tools.CloudWatchLogs -Force
        # Install-Module AWS.Tools.SecretsManager -Force
        # Import-Module AWS.Tools.Installer
        Install-Module -Name AWS.Tools.CloudWatchLogs -Force
        Install-Module -Name AWS.Tools.SecretsManager -Force
        Install-Module -Name AWS.Tools.EC2 -Force
        Install-Module -Name AWS.Tools.SecurityToken -Force
        # Install-AWSToolsModule AWS.Tools.CloudWatchLogs,AWS.Tools.SecretsManager -Force -CleanUp
        # Import-Module AWS.Tools.CloudWatchLogs
        # Import-Module AWS.Tools.SecretsManager
        
        # For using Add-Computer cmdlet
        Import-Module Microsoft.PowerShell.Management -UseWindowsPowerShell -WarningAction SilentlyContinue
        # For using Cim
        Import-Module CimCmdlets
        
        
        $inputPayload = $env:InputPayload | ConvertFrom-Json
        [string]$hostnameparam = $inputPayload.host_name
        [string]$secretparam = $inputPayload.secret_set
        
        class Logger {
            #----------------------------------------------
            [string] hidden  $cwlGroup
            [string] hidden  $cwlStream
            [string] hidden  $sequenceToken
            #----------------------------------------------
            # Log Initialization
            #----------------------------------------------
            Logger([string] $Action, [string] $HostName) {
                $this.cwlGroup = "/ps/boot/configuration/"
                $this.cwlStream = "{0}/{1}/{2}" -f $HostName, $Action,
                (Get-Date -UFormat "%Y-%m-%d_%H.%M.%S")
                $this.sequenceToken = ""
                #------------------------------------------
                if ( !(Get-CWLLogGroup -LogGroupNamePrefix $this.cwlGroup) ) {
                    New-CWLLogGroup -LogGroupName $this.cwlGroup
                    Write-CWLRetentionPolicy -LogGroupName $this.cwlGroup -RetentionInDays 3
                }
                if ( !(Get-CWLLogStream -LogGroupName $this.cwlGroup -LogStreamNamePrefix $this.cwlStream) ) {
                    New-CWLLogStream -LogGroupName $this.cwlGroup -LogStreamName $this.cwlStream
                }
            }
            #----------------------------------------
            [void] WriteLine([string] $msg) {
                $logEntry = New-Object -TypeName "Amazon.CloudWatchLogs.Model.InputLogEvent"
                #-----------------------------------------------------------
                $logEntry.Message = $msg
                $logEntry.Timestamp = (Get-Date).ToUniversalTime()
                if ("" -eq $this.sequenceToken) {
                    # First write into empty log...
                    $this.sequenceToken = Write-CWLLogEvent -LogGroupName $this.cwlGroup `
                        -LogStreamName $this.cwlStream `
                        -LogEvent $logEntry
                }
                else {
                    # Subsequent write into the log...
                    $this.sequenceToken = Write-CWLLogEvent -LogGroupName $this.cwlGroup `
                        -LogStreamName $this.cwlStream `
                        -SequenceToken $this.sequenceToken `
                        -LogEvent $logEntry
                }
            }
        }
        [Logger]$log = [Logger]::new("UserData", $hostnameparam)
        $log.WriteLine("------------------------------")
        $log.WriteLine("Log Started - V4.0")
        $RunUser = $env:username
        $log.WriteLine("PowerShell session user:" + $RunUser + " executed.")
        $log.WriteLine("Loading Secret <" + $secretparam + ">")
        $SecretObj = (Get-SECSecretValue -SecretId $secretparam -ErrorVariable SecretObjProcessError)
        if ($SecretObjProcessError) {
            $log.WriteLine($SecretObjProcessError)
            $log.WriteLine("Could not load secret <" + $secretparam + "> - terminating execution")
            return
        }
        
        [PSCustomObject]$Secret = ($SecretObj.SecretString  | ConvertFrom-Json)
        $password = $Secret.password | ConvertTo-SecureString -asPlainText -Force
        $username = $Secret.username
        $credential = New-Object System.Management.Automation.PSCredential($username, $password)
        $log.WriteLine("Domain (from Secret): <" + $Secret.domainname + ">")
        # Verify domain membership
        $compSys = Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -Property *
        #------------------------------------------------------------------------------
        if ( ($compSys.PartOfDomain) -and ($compSys.Domain -eq $Secret.domainname)) {
            $log.WriteLine("Already member of: <" + $compSys.Domain + "> - Verifying RSAT Status")
            $RSAT = (Get-WindowsFeature RSAT-AD-PowerShell)
            if ($null -eq $RSAT) {
                $log.WriteLine("<RSAT-AD-PowerShell> feature not found - terminating script")
                return
            }
            if ( (-Not $RSAT.Installed) -and ($RSAT.InstallState -eq "Available") ) {
                $log.WriteLine("Installing <RSAT-AD-PowerShell> feature")
                Install-WindowsFeature RSAT-AD-PowerShell
            }
            $comProp = (Get-ADComputer -Identity $hostnameparam -Properties Description)
            if ($null -eq $comProp.Description) {
                $log.WriteLine("Adding instance metadata to description attribute")
                $instanceId = Get-EC2InstanceMetadata -Category instanceId
                $instanceAz = Get-EC2InstanceMetadata -Category AvailabilityZone
                $instanceAccountId = (Get-STSCallerIdentity).Account
                $instanceDescription = $instanceId + "|" + $instanceAccountId + "|" + $instanceAz
                $log.WriteLine("Adding description <" + $instanceDescription + ">")
                Set-ADComputer -Identity $hostnameparam -Credential $credential -Description $instanceDescription
            }
            # Adding DNS Suffix for WFM domain
            $dnssuffixeslist = get-DnsClientGlobalSetting
            if ($Secret.domainname -in $dnssuffixeslist.SuffixSearchList) {
                $log.WriteLine("DNS suffix  <" + $Secret.domainname  + "> already exists.")
            }
            else {
                $log.WriteLine("Existing DNS suffixes <" + $dnssuffixeslist.SuffixSearchList + ">")
                $log.WriteLine("Adding DNS suffix for <" + $Secret.domainname + ">")
                $dnssuffixeslist.SuffixSearchList += $Secret.domainname
                Set-DnsClientGlobalSetting -SuffixSearchList @($dnssuffixeslist.SuffixSearchList) -ErrorVariable DnsSuffixAddError
                if ($AddCoDnsSuffixAddErrorputerProcessError) {
                    $log.WriteLine($DnsSuffixAddError)
                    $log.WriteLine("Error occured while adding DNS suffix for <" + $Secret.domainname + "> domain")
                    return
                }
                $updateddnssuffixeslist = get-DnsClientGlobalSetting
                $log.WriteLine("Updated DNS suffixes <" + $updateddnssuffixeslist.SuffixSearchList + ">")
            }
            $log.WriteLine("Terminating script - ")
            return
        }
        # Performing Domain Join
        $log.WriteLine("Performing Domain Join")
        Rename-Computer -NewName $hostnameparam -Force
        $log.WriteLine("Attempting to join domain <" + $Secret.domainname + ">")
        
        Add-Computer -ComputerName $hostnameparam -DomainName $Secret.domainname -Credential $credential -OUPath $Secret.oupath -Restart -Force -Verbose -ErrorVariable AddComputerProcessError
        if ($AddComputerProcessError) {
            $log.WriteLine($AddComputerProcessError)
            $log.WriteLine("Error occured while performing domain join operation on host - terminating execution")
            return
        }
        
        $log.WriteLine("Requesting restart...")
9
  • Cmdlets are found using the environmental variable PSMODULEPATH. See following for AWS : aws.amazon.com/blogs/compute/… Commented Mar 2, 2023 at 17:25
  • @jdweng Perplexed. The cmdlets that are contained within the AWS.Tools modules all work fine in my script being executed from sm automation. It is just the ones that are in .NET Core (cim, add/rename computer) that are not working. I checked the $Env:PSModulePath and it has this: `C:\Users\Administrator\Documents\PowerShell\Modules;C:\Program Files\PowerShell\Modules;c:\program files\powershell\7\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules;C:\Program Files (x86)\AWS Tools\PowerShell` . What could be missing for those 3 cmdlets? Commented Mar 2, 2023 at 17:49
  • And why do those 3 cmdlets work when running them in a PS session locally on the Windows instance, but don't when executing it remotely via SM automation if I am importing all that is required in my inserted script? Do I have to add something to PSModulePath explicitly? Commented Mar 2, 2023 at 17:51
  • When checking Environment variables in the GUI instead of via the terminal I noticed that in the System variables, Path contained C:\Program Files\PowerShell\7\, BUT PSModulePath did not contain that. So I added C:\Program Files\PowerShell\7\ to the PSModulePath also. Commented Mar 2, 2023 at 18:00
  • Still fails with aforementioned changes. I am starting to suspect it is an IAM problem with my AutomationServiceRole. I checked net localgroup Administrators and ssm user is not in there. Commented Mar 2, 2023 at 18:11

2 Answers 2

1

I also faced this issue and the workaround i found was to copy the modules needed from where they are installed to the path indicated in the PSModulePath for the user used by ssm document.

  1. See where module is installed

    Get-Module -ListAvailable -Name "{module_name}" | Select-Object Name, Version, Path

  2. See the value for PSModulePath

    Write-Output $Env:PSModulePath

  3. Copy the module for the installed path to the PSModulePath

    Copy-Item -Path "{installed_path}" -Destination "{ps_module_path}" -Recurse

Sign up to request clarification or add additional context in comments.

Comments

0

I would recommend a few things:

  1. Change the schemaVersion from: '0.3' > '2.2'

  2. Change the action from aws:executeScript to aws:runPowerShellScript

  3. Add targetType: /EC2

  4. This PowerShell runs with version >5 You can always upgrade the version if you wish. For simplicity of the example, I will use choco to install necessary softwares

Source: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-document.html

Working code example:

The current script connects to an EC2 when ready, renames the computer, enables SSH, upgrades pwsh, and installs AWS CLI. Send a completion signal back to CloudFormation using WaitHandleUrl

WindowsScript:
    Type: AWS::SSM::Document
    Properties:
      DocumentType: Command
      Name: WindowsScript
      TargetType: /EC2
      Content:
        schemaVersion: '2.2'
        description: 'Bootstrap Windows instance'
        parameters:
          computerName:
            type: String
            description: 'Computer name'
          userPassword:
            type: String
            description: 'Password for the user'
          WaitHandleUrl:
            type: String
            description: 'URL of the WaitConditionHandle'
        mainSteps:
          - action: aws:runPowerShellScript
            name: bootstrapWindowsEC2
            inputs:
              runCommand:
                - |
                  $Global:ErrorActionPreference = "Stop"
                  Start-Transcript -Path "C:\PerfLogs\bootstrap.log" -Append
                  Write-Output "Running bootstrap..."
                  $url = '{{WaitHandleUrl}}'
                  $body = '{"Status" : "SUCCESS", "UniqueId" : "Ready", "Data" : "Bootstrap Done"}'

                  try {
                      Rename-Computer -NewName {{computerName}} -Force

                      Write-Output "Enabling SSH"
                      Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
                      Start-Service sshd
                      Set-Service -Name sshd -StartupType 'Automatic'
                      New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
                      New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force
                      Write-Output "Done with SSH"

                      Write-Output "Creating new user"
                      $securePassword = ConvertTo-SecureString -String "{{userPassword}}" -AsPlainText -Force
                      New-LocalUser -Name you-user-name -Password $securePassword -Description "New user" -AccountNeverExpires
                      Add-LocalGroupMember -Group "Administrators" -Member vagrant
                      Write-Output "Done with vagrant user"

                      # Set execution policy to allow script execution
                      Write-Output "Set execution policy"
                      Set-ExecutionPolicy Bypass -Scope Process -Force
                      Write-Output "Done with exec policy"

                      Write-Output "Installing pwsh7..."
                      choco install -y powershell-core --install-arguments='DISABLE_TELEMETRY=1'
                      Write-Output "Done with pwsh7."

                      Write-Output "Installing AWS CLI version {{AWSCLIVersion}}"
                      choco install -y --force awscli --version={{AWSCLIVersion}}
                      Write-Output "Done installing AWS CLI"

                  } catch {
                      $body = '{"Status" : "FAILURE", "UniqueId" : "Fail", "Data" : "Completed with errors. Exception: $($_.Exception.GetType().FullName)"}'
                      Write-Error "Failed with exception: $($_.Exception)"
                  } finally {
                      Invoke-RestMethod -Uri $($url) -Method PUT -Body $($body) -ContentType 'application/json'
                      Stop-Transcript
                  }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.