AWS Lambda for PowerShell Encrypted Environment Variables


Recently I was developing a AWS Lambda for PowerShell function to read a table from a Microsoft SQL on RDS Instance within a VPC. Using the .NET Core SQL client I need to provide credentials to access the database. For the password it should be stored encrypted at rest, never in plain text, and when passed to the function should be done so as a secure string. We can configure the function to use KMS for encryption at rest. This presents us with two challenges. How do I encrypt a AWS Lambda environment variable and how will my AWS Lambda for PowerShell function securely get the decrypted value in memory as a secure string when the Lambda function is invoked.

Encrypting an AWS Lambda Environment Variable

Utilizing the AWS Lambda console when configuring the function, under environment variable, select the option to “Enable helpers for encryption in transit”. You’ll need to provide what KMS key you’ll want to encrypt the value with. When we enter a value and select encrypt the console encrypts the value with KMS and stores the value as an encrypted base 64 text string.


Enter the unencrypted environment variable and click Encrypt


Save and refresh the console to see the value as a encrypted base 64 text string


Securely Decrypting Variables with AWS Lambda for PowerShell

So far this is easy however if we want to access the variable we have a problem. The console provides a “code” link for the Lambda function but our PowerShell function is executed in a .NET Core environment and the console displays the helper code as C# which isn’t going to work in my PowerShell function. Thankfully since PowerShell is just a layer of abstraction from C# I was able to create a PowerShell function to convert the encrypted variable to a secure string utilizing the .NET Core KMS client.

function ConvertFrom-KmsEncryptedString
    $SecureString = ConvertFrom-KmsEncryptedString -EncryptedBase64String $env:password
    .PARAMETER EncryptedBase64String
    Encrypted base 64 string to be decrypted.
    .PARAMETER ToPlainText
    Default output is a secure string object, us this to output as a plain text string.
    [switch]$ToPlainText = $false
begin {
    #KMS Client Object
    $KmsClient = New-Object Amazon.KeyManagementService.AmazonKeyManagementServiceClient
    #Convert encrypted base 64 text string to and encrypted bytes array. 
    $EncryptedBytes = [System.Convert]::FromBase64String($EncryptedBase64String)
    $utf8 = [System.Text.UTF8Encoding]::UTF8
    $SecureString = [System.Security.SecureString]::new()
process {
    foreach ($byte in $KmsClient.Decrypt($EncryptedBytes,$null)) {
    #Disposing the KMS client to release its resources.
    #Makes the secure string value immutable.
end {
    if ($ToPlainText.IsPresent -eq $true) {
        #Returns the plain text string of the encrypted environment variable when -PlainText switch is used.
        Return [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecurestringToBstr($SecureString))
    else {
        #Returns a secure string of the decrypted environment variable.
        Return $SecureString

The function input will be the $env:password environment variable key containing the encrypted base 64 text string. The default output will be a secure string object unless you use the flag for output as plain text. Here’s an example of the function being used to pass a secure string to my other function which connects to the Microsoft SQL database.

$SecureString = ConvertFrom-KmsEncryptedString -EncryptedBase64String $env:password
$Datatable = Get-SqlToDataTable -Instance $env:instance -Database $env:database -Table $env:table -UserName $env:username -Password $SecureString


Utilizing this method our secure value is never stored as plain text. It’s passed to the Lambda execution environment as an encrypted base 64 text value. The ConvertFrom-KmsEncryptedString function uses the KMS client over HTTPS and loads the decrypted bytes into a secure string so it’s protected in memory. I experimented with lots of methods to do this as securely and easily as possible. One consideration is that if I used the KMS client async decrypt the value is returned as a unencrypted memory stream object in the response.I looked at using crypto streams but was overly complicated and had more overhead. Using the KMS client synchronous decrypt call and appending the bytes to the secure string gave me the desired results with the least amount of complexity.

Let me know if you have any recommendations or suggestions. Thank you!

7 thoughts on “AWS Lambda for PowerShell Encrypted Environment Variables

  1. I had this working yesterday following this tutorial, but today it was no longer working. I was wondering if something on AWS changed? I get the following error message when my lambda executes: “[Error] – Exception calling “Decrypt” with “2” argument(s): “A task was canceled.”” The error is arising from this block of code:

    foreach ($byte in $KmsClient.Decrypt($EncryptedBytes,$null)) {


    • Chris Fitch

      TaskCancelledException usually occurs when the Lambda function duration hit the function timeout. Being that it worked yesterday and not today it’s probably because of function cold start. Try increasing the timeout value on the Lambda function.


    • Chris Fitch

      Interesting. When I looked at the API documentation for the Decrypt API call the exception you mentioned isn’t listed as one of the possible options. I searching for task canceled exception and found there’s an issue log for the .NET SDK task waiter that might be the issue. Are you using the latest version of PS Core and the AWS SDK for .NET Core? If you used the PowerShell command to build the Lambda function it does it locally and then pushed to S3. Which could explain why it’s happening local and on Lambda. The only other thing I can think of is you’re having permission issues to KMS. Maybe the encryption helper got changed, the CMK used by the function to decrypt the base64encoded text. Also if you set encryption context that could be the issue, but unlikely since you said it did work then it didn’t.


      • Chris Fitch

        I just tested it on Lambda function I have deployed and on my local box and it’s working for me. I’m not sure what could have changed that it worked one day then not the next. Do you have the log data from the Lambda function in one logs in CloudWatch logs?


  2. bhumagupta

    Hi ,
    Can we Configure the script to support ephemeral storage for Windows 2016 ..

    i haev to achieve this using Powershell script.

    KMS Encryption.


  3. bhumagupta

    Set-ExecutionPolicy Unrestricted -Force
    $Ireland_S3Bucket = “novartisrccgbieinfra001”
    $NVirginia_S3Bucket = “novartisrccgbusnvinfra001”
    $Ireland_Suffixes = “”
    $NVirginia_Suffixes = “”
    $Region = Invoke-RestMethod -Uri “”
    echo $region
    $USEast1_Proxy = “”
    $EUWest1_Proxy = “”
    if ($Region -like “us-east-1*”) {
    $proxy_url = (“http://” + $USEast1_Proxy + “:2010”)
    $Bucket = $NVirginia_S3Bucket
    $suffixes = $NVirginia_Suffixes
    $proxy = $USEast1_Proxy
    $Region1 = “us-east-1”
    elseif ($Region -like “eu-west-1*”) {
    $proxy_url = (“http://” + $EUWest1_Proxy + “:2010”)
    $Bucket = $Ireland_S3Bucket
    $suffixes = $Ireland_Suffixes
    $proxy = $EUWest1_Proxy
    $Region1 = “eu-west-1”
    #IE Proxy Setup
    $ProxyRegistry = “HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings”
    Set-ItemProperty -Path $ProxyRegistry -Name ProxyEnable -Value 1
    Set-ItemProperty -Path $ProxyRegistry -Name ProxyServer -Value “$proxy_url”
    Set-ItemProperty -Path $ProxyRegistry -Name ProxyOverride -Value “;”
    #Powershell Proxy Setup
    $powershellclient = new-object
    $WebProxy = New-Object System.Net.WebProxy(“$proxy_url”,$true)
    $powershellclient.Proxy = $WebProxy
    #Part 1: Download the MSI
    new-item C:\Scripts -type directory
    Import-Module AWSPowerShell
    #Part 2: Get-STS Credentials from another AWS Account
    #Define Proxy for AWS cmdlet invocations
    Set-AWSProxy -Hostname $proxy -Port 2010 -Bypasslist “”
    $STS = (Use-STSRole -RoleSessionName “Session1” -RoleArn “arn:aws:iam::304512965277:role/RRCC_AWS_EC2WINSBX” -DurationInSeconds 3600 -Region $Region1)
    #Download S3 object
    Cd $ScriptDirectory
    $localPath = $ScriptDirectory
    $keyPrefix = “Windows/Sandbox/LGPO/LGPO.exe”
    $keyPrefix1 = “Windows/Sandbox/LGPO/{C5E7B73D-A34D-4AC2-BCB4-7116F60AD103}.zip”
    $keyprefix2 = “Windows/Sandbox/LGPO/LGPO1.ps1”
    $keyprefix3 = “Windows/Sandbox/ClamAV-x64/”
    $keyprefix4 = “Windows/Sandbox/ClamAV-Conf/clamav-0.99.1-x64.msi”
    #$proxyObject.Address = “″
    #Copy S3 objects to local folder named C:\Scripts”
    Copy-S3Object -BucketName $Bucket -Key $keyPrefix -LocalFolder $localPath -Credential $STS.Credentials -Force
    Copy-S3Object -BucketName $Bucket -Key $keyPrefix1 -LocalFolder $localPath -Credential $STS.Credentials -Force
    Copy-S3Object -BucketName $Bucket -Key $keyPrefix2 -LocalFolder $localPath -Credential $STS.Credentials -Force
    Copy-S3Object -BucketName $Bucket -Key $keyPrefix3 -LocalFolder $localPath -Credential $STS.Credentials -Force
    Copy-S3Object -BucketName $Bucket -Key $keyPrefix4 -LocalFolder $localPath -Credential $STS.Credentials -Force
    #Unzip LGPO Files to C:\Scripts\LGPO
    $shell = New-Object -ComObject shell.application
    $zip = $shell.NameSpace(“C:\Scripts\Windows\Sandbox\LGPO\{C5E7B73D-A34D-4AC2-BCB4-7116F60AD103}.zip”)
    foreach($item in $zip.items()){
    $Action = New-ScheduledTaskAction -Execute ‘powershell.exe’ -Argument “-file C:\Scripts\Windows\Sandbox\LGPO\LGPO1.ps1”
    $Trigger = New-ScheduledTaskTrigger -AtLogOn
    $Task = New-ScheduledTask -Action $Action -Trigger $Trigger
    $Task | Register-ScheduledTask -TaskName ‘LGPO’
    #Clam AV
    mkdir c:\ClamAV-Conf
    mkdir c:\ClamAV-x64
    mkdir c:\ClamAV-x64\db
    mkdir c:\ClamAV-x64\log
    $clamdconf = “TCPAddr
    TCPSocket 3310
    MaxThreads 2
    LogTime true
    LogFile c:\ClamAV-x64\log\clamd.log
    DatabaseDirectory C:\ClamAV-x64\db”
    $clamdconf | Set-Content -Path c:\ClamAV-Conf\clamd.conf
    $freshcalmconf = “DatabaseDirectory c:/ClamAV-x64/db/
    MaxAttempts 3
    HTTPProxyServer $proxy
    HTTPProxyPort 2010
    NotifyClamd c:\ClamAV-Conf\clamd.conf
    LogFileMaxSize 20480000
    LogTime true
    UpdateLogFile c:\ClamAV-x64\log\freshclam.log”
    $freshcalmconf | Set-Content -Path c:\ClamAV-Conf\freshclam.conf
    #ClamAV downlad
    Copy-Item $ScriptDirectory\$keyPrefix3 C:\ClamAV-Conf\
    #Unzip RunasSVC files to folder C:\ClamAv-Conf
    $runassvcshell = New-Object -ComObject shell.application
    $runassvczip = $runassvcshell.NameSpace(“C:\ClamAV-Conf\”)
    foreach($item in $runassvczip.items()){
    # Start Clamd as service in Services.msc
    Start-Process -Wait -filepath “C:\ClamAV-Conf\runassvc\RunAsSvc.exe” -ArgumentList “-i –displayname Clamd Antivirus –description ClamAntivirus –exe C:\ClamAV-x64\clamd.exe –params -c c:\ClamAV-Conf\clamd.conf –workingdir C:\ClamAV-x64 –quiet”
    #Start download MSI installation file of clam antivirus 64bit edition to C:\ClamAV-x64 and starts silent installation
    Copy-Item $ScriptDirectory\$keyPrefix4 C:\ClamAV-x64\
    Start-Process -Wait -filepath “C:\Windows\System32\msiexec.exe” -ArgumentList “/i C:\ClamAV-x64\clamav-0.99.1-x64.msi /quiet TARGETDIR=C:\ClamAV-x64\ /log C:\ClamAV-x64\clamav_installation.txt”
    # Start download of clamd database file from internet
    Start-Process -Wait -filepath “C:\ClamAV-x64\freshclam.exe” -ArgumentList “–config-file c:\ClamAV-Conf\freshclam.conf -l c:\ClamAV-x64\log\dbupdate.log”
    start-process -Wait -filepath “c:\ClamAV-x64\clamscan.exe” -ArgumentList “-d c:\ClamAV-x64\db\ -l c:\ClamAV-x64\log\scan.log –recursive”
    #Schedule Antivirus SCAN for every 7Pm at Thurday of a week
    $ClamAVAction = New-ScheduledTaskAction -Execute ‘c:\ClamAV-x64\clamscan.exe’ -Argument “-d c:\ClamAV-x64\db\ -l c:\ClamAV-x64\log\scan.log –recursive” -WorkingDirectory c:\
    $ClamAVTrigger = New-ScheduledTaskTrigger -Weekly -At 7PM -DaysOfWeek Thursday
    $ClamAVPrincipal =New-ScheduledTaskPrincipal “$env:COMPUTERNAME\$env:username” -LogonType Interactive
    $ClamAVTask = New-ScheduledTask -Action $ClamAVAction -Trigger $ClamAVTrigger
    $ClamAVTask | Register-ScheduledTask -TaskName “CLAMAVSCAN” -User $ClamAVPrincipal.UserId
    #Schedule Antivirus Database SYNC at 6pm daily basis
    $ClamAVDbSyncAction = New-ScheduledTaskAction -Execute ‘C:\ClamAV-x64\freshclam.exe’ -Argument “–config-file c:\ClamAV-Conf\freshclam.conf -l c:\ClamAV-x64\log\dbupdate.log”
    $ClamAVDbSyncTrigger = New-ScheduledTaskTrigger -Daily -At 6PM
    $ClamAVDbSyncPrincipal = New-ScheduledTaskPrincipal “$env:COMPUTERNAME\$env:username” -LogonType Interactive
    $ClamAVDbSyncTask = New-ScheduledTask -Action $ClamAVDbSyncAction -Trigger $ClamAVDbSyncTrigger
    $ClamAVDbSyncTask | Register-ScheduledTask -TaskName “ClamAVDbSync” -User $ClamAVDbSyncPrincipal.UserId
    #Local Administrator Password change at next logon
    $username = ‘Administrator’
    $user = [adsi] “WinNT://./$username”
    $user.UserFlags = $user.UserFlags[1] -bor $ADS_UF_DONT_EXPIRE_PASSWD
    $user.passwordExpired = 1
    $registryPath = “HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp”
    $Name1 = “UserAuthentication”
    $value1 = “00000000”
    IF(!(Test-Path $registryPath))
    New-Item -Path $registryPath -Force | Out-Null
    New-ItemProperty -Path $registryPath -Name $name1 -Value $value1 -PropertyType DWORD -Force | Out-Null
    New-ItemProperty -Path $registryPath -Name $name1 -Value $value1 -PropertyType DWORD -Force | Out-Null
    $Name2 = “SecurityLayer”
    $value2 = “00000000”
    IF(!(Test-Path $registryPath))
    New-Item -Path $registryPath -Force | Out-Null
    New-ItemProperty -Path $registryPath -Name $name2 -Value $value2 -PropertyType DWORD -Force | Out-Null
    New-ItemProperty -Path $registryPath -Name $name2 -Value $value2 -PropertyType DWORD -Force | Out-Null
    #SSL 3.0 Settings
    $SSLregistryPathServer = “HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Server”
    $SSLServerName = “Enabled”
    $SSLServervalue = “00000000”
    IF(!(Test-Path $SSLregistryPathServer))
    New-Item -Path $SSLregistryPathServer -Force | Out-Null
    New-ItemProperty -Path $SSLregistryPathServer -Name $SSLServerName -Value $SSLServervalue -PropertyType DWORD -Force | Out-Null
    New-ItemProperty -Path $SSLregistryPathServer -Name $SSLServerName -Value $SSLServervalue -PropertyType DWORD -Force | Out-Null
    $SSLregistryPathClient = “HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Client”
    $SSLClientName = “Enabled”
    $SSLClientvalue = “00000000”
    IF(!(Test-Path $SSLregistryPathClient))
    New-Item -Path $SSLregistryPathClient -Force | Out-Null
    New-ItemProperty -Path $SSLregistryPathClient -Name $SSLClientName -Value $SSLClientvalue -PropertyType DWORD -Force | Out-Null
    New-ItemProperty -Path $SSLregistryPathClient -Name $SSLClientName -Value $SSLClientvalue -PropertyType DWORD -Force | Out-Null
    #Hostaname Must be changed based on IP Regex Pattern
    $IP=((ipconfig | findstr [0-9].\.)[0]).Split()[-1]
    $HOSTNAME= “IP-” + ($IP.Replace(“.”,”-“))
    if ([Regex]::IsMatch(“$HOSTNAME”,”$REGEX”))
    Rename-Computer -NewName $HOSTNAME
    #First store the suffixes to set in a variable
    # $suffixes = ‘’
    #Since this is a static method, get a class object and then call the method.
    $class = [wmiclass]’Win32_NetworkAdapterConfiguration’
    Restart-Computer -Force -ComputerName $env:COMPUTERNAME
    else {
    echo “No hostname changed using default”
    $FQDN= $HOSTNAME + “.” + $suffixes
    $file = “$env:windir\System32\drivers\etc\hosts”
    “$IP $FQDN” | Add-Content -PassThru $file
    echo “Final”


Leave a Reply to Drew (@dbudwin) Cancel reply

Please log in using one of these methods to post your comment: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.