Creating AWS SSM Document to run Powershell commands - amazon-web-services

Im trying to make an SSM Document that runs the following Powershell script:
New-Item -ItemType Directory -Force -Path C:\temp
$checkupdatesscript = "`$UpdateSession = New-Object -ComObject Microsoft.Update.Session"
$checkupdatesscript | Out-File C:\temp\checkwindowsupdates.ps1
Add-Content -Path C:\temp\checkwindowsupdates.ps1 -Value "`$UpdateSearcher = `$UpdateSession.CreateupdateSearcher()"
Add-Content -Path C:\temp\checkwindowsupdates.ps1 -Value "`$Updates = #(`$UpdateSearcher.Search(`"IsHidden=0 and IsInstalled=0`").Updates)"
Add-Content -Path C:\temp\checkwindowsupdates.ps1 -Value "`$Updates | Select-Object Title > C:\temp\windowsupdates.txt"
$action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument "C:\temp\checkwindowsupdates.ps1"
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5)
Register-ScheduledTask -Action $action -Trigger $trigger -TaskName "Check Windows Updates" -Description "checks for any outstanding windows updates every 5 minutes"
I have created the following SSM Document in YAML:
---
schemaVersion: "2.2"
description: "Creates script and scheduled task to check for any outstanding windows updates every 5 minutes"
mainSteps:
- action: "aws:runPowerShellScript"
name: "RunCommands"
inputs:
runCommand:
- "New-Item -ItemType Directory -Force -Path C:\temp"
- "$checkupdatesscript = \"`$UpdateSession = New-Object -ComObject Microsoft.Update.Session\""
- "$checkupdatesscript | Out-File C:\temp\checkwindowsupdates.ps1"
- "Add-Content -Path C:\temp\checkwindowsupdates.ps1 -Value \"`$UpdateSearcher = `$UpdateSession.CreateupdateSearcher()\""
- "Add-Content -Path C:\temp\checkwindowsupdates.ps1 -Value \"`$Updates = #(`$UpdateSearcher.Search(`\"IsHidden=0 and IsInstalled=0`\").Updates)\""
- "Add-Content -Path C:\temp\checkwindowsupdates.ps1 -Value \"`$Updates | Select-Object Title > C:\temp\windowsupdates.txt\""
- "$action = New-ScheduledTaskAction -Execute \"Powershell.exe\" -Argument \"C:\temp\checkwindowsupdates.ps1\""
- "$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5)"
- "Register-ScheduledTask -Action $action -Trigger $trigger -TaskName \"Check Windows Updates\" -Description \"checks for any outstanding windows updates every 5 minutes\""
But am receiving an error when attempting to create the document. The error is "InvalidDocumentContent: null"
I am assuming I have messed up the YAML but can't seem to figure out what.

The issue did lie within the YAML. I had not escaped the backslashes in the file paths. The correct yaml should look like this:
---
schemaVersion: "2.2"
description: "Creates script and scheduled task to check for any outstanding windows updates every 5 minutes"
mainSteps:
- action: "aws:runPowerShellScript"
name: "RunCommands"
inputs:
runCommand:
- "New-Item -ItemType Directory -Force -Path C:\\temp"
- "$checkupdatesscript = \"`$UpdateSession = New-Object -ComObject Microsoft.Update.Session\""
- "$checkupdatesscript | Out-File C:\\temp\\checkwindowsupdates.ps1"
- "Add-Content -Path C:\\temp\\checkwindowsupdates.ps1 -Value \"`$UpdateSearcher = `$UpdateSession.CreateupdateSearcher()\""
- "Add-Content -Path C:\\temp\\checkwindowsupdates.ps1 -Value \"`$Updates = #(`$UpdateSearcher.Search(`\"IsHidden=0 and IsInstalled=0`\").Updates)\""
- "Add-Content -Path C:\\temp\\checkwindowsupdates.ps1 -Value \"`$Updates | Select-Object Title > C:\\temp\\windowsupdates.txt\""
- "$action = New-ScheduledTaskAction -Execute \"Powershell.exe\" -Argument \"C:\\temp\\checkwindowsupdates.ps1\""
- "$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5)"
- "Register-ScheduledTask -Action $action -Trigger $trigger -TaskName \"Check Windows Updates\" -Description \"checks for any outstanding windows updates every 5 minutes\""

Related

AWS - Security groups reports powershell

I'm trying to pull several region such as us-west-1, us-west-2 or everything security groups from aws but it doesn't seem to be working and it is getting the region that was defined in ./config
is there a way to pull it from all region? and how should i do it?
that is what i have done so far.
Import-Module AWSPowershell
> $EC2GroupList = Get-EC2SecurityGroup | Select-Object Description, GroupId, GroupName,
> IpPermission, IpPermissionsEgress, VpcId
>
> $ChangeList = #()
>
> $GroupsCount = ($EC2GroupList | Measure-Object).Count
> foreach($SecGroup in $EC2GroupList) {
> $Description = $SecGroup.IpPermission.Ipv4Ranges.Description
> $GroupID = $SecGroup.GroupId
> $SecurityGroupName = $SecGroup.GroupName
> $SourceIP = $SecGroup.IpPermission.Ipv4Ranges.CidrIp
> $FromPort = $SecGroup.IpPermission.FromPort
> $ToPort = $SecGroup.IpPermission.ToPort
> $Protocol = $SecGroup.IpPermission.IpProtocol
>
> echo $GroupsCount
>
> if($FromPort -ne $null) {
> $NewObject = new-object -TypeName PSobject
> #$NewObject | Add-Member NoteProperty -Name Region -Value
> $NewObject | Add-Member NoteProperty -Name Description -Value $Description
> $NewObject | Add-Member NoteProperty -Name GroupID -Value $GroupID
> $NewObject | Add-Member NoteProperty -Name SecurityGroupName -Value $SecurityGroupName
> $NewObject | Add-Member NoteProperty -Name SourceIP -Value $SourceIP
> $NewObject | Add-Member NoteProperty -Name FromPort -Value $FromPort
> $NewObject | Add-Member NoteProperty -Name ToPort -Value $ToPort
> $NewObject | Add-Member NoteProperty -Name Protocol -Value $Protocol
> $ChangeList += $NewObject
> }
> }
>
> echo $ChangeList
The API calls are mostly region specific except for a few global services so you need make a call per region generally. You can override the region like this: Get-EC2SecurityGroup -region eu-west-2.
Heres a rough sample
# wrap up your existing code in a function for later...
function Get-RegionSecurityGroupReport{
Param([String] $region = 'eu-west-1');
# replace with your code but pass $region when calling any APIs e.g.
$EC2GroupList = Get-EC2SecurityGroup -region $region | Select-Object ...
# return current regions data
return $EC2GroupList;
}
# Setup the script
$activeRegions = #('eu-west-1', 'eu-west-2');
$results = #();
# loop through regions and execute your function with each region in turn...
$activeRegions | % {
Write-Host "Processing region $($_)";
$results += Get-RegionSecurityGroupReport -region $_;
}
# your multi regional report:
Write-Host $results
There is an API call DescribeRegions gets you all the regions available and the corresponding aws cli call describe-regions.
I found this blog post aws-regions-and-windows-powershell and the corresponding call Get-EC2Region.

EC2 User data - Disable it after all conditions are met in user data

I'm trying to rename hostname and add to AD of a spot instance. It is a simple powershell script. I've read the docs that by default user data will be disable after it gets executed once and if <persist>true</persist> is used it will not be disabled.
I think I saw somewhere this(enabling to be run at each startup) is done via taskscheduler but can't find the link.
Can someone point me to the task scheduler job or the way to manually disable the userdata once my if conditions are met.
<powershell>
Set-ExecutionPolicy unrestricted -Force
$instanceName = "test-name5"
$username = "domain\username"
$password = "password" | ConvertTo-SecureString -AsPlainText -Force
$cred = New-Object -typename System.Management.Automation.PSCredential($username, $password)
Start-Sleep -s 5
$hostname = hostname
$domain = (Get-WmiObject win32_computersystem).Domain
if (!($hostname -eq $instanceName)){
Rename-Computer -NewName $instanceName -restart -force
}Elseif (!($domain -eq 'my.domain.local')){
Start-Sleep -s 5
Add-Computer -DomainName my.domain.local -OUPath "OU=Windows,OU=QAServers,OU=Servers,DC=my,DC=domain,DC=local" -Credential $cred -Force -Restart -erroraction 'stop'
}Else {
####code to disable the running of userdata once above conditions
are met####
}
</powershell>
<persist>true</persist>
It's worth reading the ec2config-service documentation, as the setting you want is referenced in there.
You want the Ec2HandleUserData setting, which is configured in the Config.xml.
Powershell can easily update this setting:
$path = 'C:\Program Files\Amazon\Ec2ConfigService\Settings\config.xml'
$xml = [xml](Get-Content $path)
$state = $xml.Ec2ConfigurationSettings.Plugins.Plugin | where {$_.Name -eq 'Ec2HandleUserData'}
$state.State = 'Disabled'
$xml.Save($path)
I use this code when creating custom AMI's to re-enable userdata handling ($state.State = 'Enabled').
EDIT: The above is for ec2config not ec2launch which is what the OP is using. I'd missed this originally.
I this case I think you need to change the way your script runs, rather than use <persist> and then try to disable its functionality, I would remove the persist tag and call InitializeInstance.ps1 –Schedule (documentation link) in your if for the conditions you want the userdata to re-run:
if ($hostname -ne $instanceName) {
& C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 -Schedule
Rename-Computer -NewName $instanceName -Restart -Force
}
elseif ($domain -ne 'my.domain.local') {
& C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 -Schedule
Add-Computer -DomainName aws.macmillan.local -OUPath "OU=Windows,OU=QAServers,OU=Servers,DC=my,DC=domain,DC=local" -Credential $cred -Force -Restart -ErrorAction 'stop'
}
As I said in the comments of the previous answer, I had 3 options and since I found the aws scheduled task I went with the last option. Answering my own question since it'll be easy to spot the code.
<powershell>
Set-ExecutionPolicy unrestricted -Force
#Enter instance hostname here
$instanceName = "test-name8"
$username = "domain\username"
#Using ssm parameter store to avoid having the password in plaintext
$password = (Get-SSMParameterValue -Name AD-Password -WithDecryption $True -Region us-east-1).Parameters[0].Value | ConvertTo-SecureString -asPlainText -Force
Start-Sleep -s 3
$cred = New-Object -typename System.Management.Automation.PSCredential($username, $password)
Start-Sleep -s 5
$hostname = hostname
$domain = (Get-WmiObject win32_computersystem).Domain
if ($hostname -ne $instanceName){
Rename-Computer -NewName $instanceName -restart -force
}Elseif ($domain -ne 'my.domain.local'){
Start-Sleep -s 5
Add-Computer -DomainName my.domain.local -OUPath "OU=Windows,OU=QAServers,OU=Servers,DC=my,DC=domain,DC=local" -Credential $cred -Force -Restart -erroraction 'stop'
}Else {
Disable-ScheduledTask -TaskName "Amazon Ec2 Launch - Userdata Execution"
Unregister-ScheduledTask -TaskName "Amazon Ec2 Launch - Userdata Execution"
}
</powershell>
<persist>true</persist>
note: a role that has ssm policies must be attached while launching the server for this ssm parameter command to work.
I was solving similar issue and I had to change Windows Server 2016 hostname and enroll it to Elastic Server Fleet. Also I could not allow instance to be rebooted. I used this code to solve this.
NB. I understand that it is not direct way of doing this and has numerous drawbacks, but in my circumstances goal was achieved without negative impact.
<powershell>
$ComputerName = "MyPCRandomName"
Set-ItemProperty -path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" -name "Hostname" -value $ComputerName
elastic-agent enroll --enrollment-token 123 --url=321
</powershell>

I need to create to Redundant VMs (both on 2 different datastores) via function

I have defined all the needed variables in a separate Variable_Defination.ps1 file and have called it up here.
Is there a better way to do this?
I just want a script to create 2 redundant VMs both running on separate datastores and having Affinity rule set up for disaster recovery cases
. ./Variable_Defination.ps1
function CreateRedundantVMs {
Param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[string]$ClusterName,
[string]$ComputerName1,
[string]$ComputerName2,
[string]$sourcetemplate,
[string]$InfraResourcePoolName,
[string]$OSCustomizationspec,
[string]$description
)
$Viabledatastores = (Get-Cluster $ClusterName | Get-Datastore) |
Where {$_.Name -like '*vSSD*'} |
Sort-Object FreeSpaceGB -Descending |
Select -First 2 -ErrorAction 'Stop'
if ($Viabledatastores) {
$Viabledatastores
} else {
Write-Host "No Viable Datastores found"
break;
}
Write-Verbose "`n---------------------- Creating redundant VMs now ------------------------`n " -Verbose
New-VM -Name $ComputerName1 -ResourcePool $InfraResourcePoolName -Datastore $Viabledatastores[0] -Description $description -Template $sourcetemplate -OSCustomizationspec $OSCustomizationspec -DiskStorageFormat Thin
New-VM -Name $ComputerName2 -ResourcePool $InfraResourcePoolName -Datastore $Viabledatastores[1] -Description $description -Template $sourcetemplate -OSCustomizationspec $OSCustomizationspec -DiskStorageFormat Thin
Start-Sleep -s 3
Write-Host "`n -------------------------------------------------------------------------`n "
Write-Verbose -Message "Virtual Machine $ComputerName1 and $ComputerName2 Deployed. Powering On" -Verbose
Write-Host "`n -------------------------------------------------------------------------`n "
Start-VM -VM $ComputerName1
Start-VM -VM $ComputerName2
}
# calling function
CreateRedundantVMs $ClusterName $ComputerName1 $ComputerName2 $sourcetemplate $InfraResourcePoolName $OSCustomizationspec $description

vCenter VM permission query using Powershell / PowerCLI

I've been trying to use Powershell with imported PowerCLI commands for VMware administration and I've hit a snag. What I'm trying to do is query all VM's in a location (doesn't matter where), and for every VM I want the group with "Virtual Machine User with Snapshot" permission, and then use that group name to run a Get-ADGroupMembers query for everyone in that group. I also have to remove the domain prefix from the AD query, which would otherwise cause an error.
After some more playing around with outputting hash table information into the csv, as opposed to 'SystemObject[]', I finally got the script so it doesn't return errors EXCEPT on VM's where there is more than one group. It throws an error but the script continues, and just outputs the members of the first group.
How do I get it to do a recursive AD query for every group that is pulled into the owner groups hashtable? The output would be the same as for all the other VM's, just with a line for each group and members.
$AllVMs = #()
$vms = get-vm * -Location datacenter
foreach ($vm in $vms)
{
$owners = Get-VIPermission $vm.name | where-object {$_.role -eq "virtual machine user with snapshot"}
foreach ($owner in $owners)
{
$members = Get-ADGroupMember ($owners.principal -replace '^prefix\\')
$temp = New-Object psobject |
Add-Member Noteproperty "Name" -value $vm.name -PassThru |
Add-Member Noteproperty "Owner" -value (#($owners.principal) -join ',') -PassThru |
Add-Member Noteproperty "Members" -value (#($members.SamAccountName) -join ',') -passthru
$AllVMs+=$temp
}
$AllVMs | Export-Csv -Path c:\users\me\desktop\AllVMs.csv
I was playing around with it some more today and figured it out! I'm running the script right now against a datacenter with 350+ machines so technically I don't know 100% that it works, but it worked against 3 machines :-) I also added a line to list every machine that's owned by more than one group - handy for troubleshooting. Here's the script:
$AllVMs = #()
$vms = get-vm -Location DATACENTER
foreach ($vm in $vms)
{
$owners = #(Get-VIPermission $vm.name | where-object {$_.role -eq "virtual machine user with snapshot"})
if ($owners.count -gt 1) {write-host "** Note ** '$vm' has"$owners.count "owner groups"}
foreach ($owner in $owners)
{
$members = Get-ADGroupMember ($owner.principal -replace '^prefix\\')
$temp = New-Object psobject |
Add-Member Noteproperty "Name" -value $vm.name -PassThru |
Add-Member Noteproperty "Owner" -value (#($owner.principal) -join ',') -PassThru |
Add-Member Noteproperty "Members" -value (#($members.SamAccountName) -join ',') -PassThru
$AllVMs+=$temp
}
}
$AllVMs
Change $owners.principal to $owner.principal after $members = Get-ADGroupMember? In case you have nested AD groups, Get-ADGroupMember has a -Recursive parameter.

Invoke-RestMethod not accepting letters with accent

I have a service on SharePoint 2013 which is responsible for managing custom menu.
I'm trying to use this service from PowerShell like this:
$MenuItem = New-Object -TypeName PSObject
$MenuItem | Add-Member -MemberType NoteProperty -Name LinkEN -Value $pageLink
$MenuItem | Add-Member -MemberType NoteProperty -Name LinkFR -Value $pageLink
$MenuItem | Add-Member -MemberType NoteProperty -Name TitleEN -Value $pageParam.ListTitle;
$MenuItem | Add-Member -MemberType NoteProperty -Name TitleFR -Value $pageParam.ListTitle;
$body = #{ _menuItem = $MenuItem; _parent = $null } | ConvertTo-Json
$url = $url + "/_vti_bin/MenuService.svc/AddMenuItem"
$result = Invoke-RestMethod -Uri $url -Method Post -UseDefaultCredentials -ContentType "application/json; charset=utf-8" -Body $body
It works fine up to the moment when I'm trying to send letter with an accent (e.g. è) through parameters (for example: $pageParam.ListTitle = "ètè").
In this situation I'm getting the following error:
Invoke-RestMethod : Request Error The server encountered an error
processing the request. See server logs for more details. At
FixDocumentPages.ps1:145 char:19
+ $result = Invoke-RestMethod -Uri $url -Method Post -UseDefaultCredential ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
I have breakpoint in the server code, but it never gets that far.
Below are the SharePoint logs from this incident:
Entering monitored scope (Request(POST: http://wssppoc:5000/_vti_bin/MenuService.svc/AddMenuItem )). Parent No Name=Request(POST:http ://wssppoc:5000/_vti_bin/MenuService.svc/AddMenuItem) 4af4519d-0879-c02e-e1ad-bb2bcbb09abb Non-OAuth request. IsAuthenticated=False, UserIdentityName=, ClaimsCount=0 4af4519d-0879-c02e-e1ad-bb2bcbb09abb Micro Trace Tags: 0 nasq,1 agb9s 4af4519d-0879-c02e-e1ad-bb2bcbb09abb
Leaving Monitored Scope (Request(POST:http ://wssppoc:5000/_vti_bin/MenuService.svc/AddMenuItem)). Execution Time=1,7839 4af4519d-0879-c02e-e1ad-bb2bcbb09abb
Entering monitored scope (Request(POST:http://wssppoc:5000/_vti_bin/MenuService.svc/AddMenuItem)). Parent No Name=Request(POST:http ://wssppoc:5000/_vti_bin/MenuService.svc/AddMenuItem) 4af4519d-087a-c02e-e1ad-b0990e08ad35 Micro Trace Tags: 0 nasq 4af4519d-087a-c02e-e1ad-b0990e08ad35
Leaving Monitored Scope (Request(POST:http://wssppoc:5000/_vti_bin/MenuService.svc/AddMenuItem)). Execution Time=0,6495 4af4519d-087a-c02e-e1ad-b0990e08ad35
Entering monitored scope (Request(POST:http://wssppoc:5000/_vti_bin/MenuService.svc/AddMenuItem)). Parent No Name=Request(POST:http ://wssppoc:5000/_vti_bin/MenuService.svc/AddMenuItem) 4af4519d-087a-c02e-e1ad-bb3e80204d39 Non-OAuth request. IsAuthenticated=True, UserIdentityName=0#.w|test\administrator, ClaimsCount=29 4af4519d-087a-c02e-e1ad-bb3e80204d39 Micro Trace Tags: 0 nasq,1 agb9s 4af4519d-087a-c02e-e1ad-bb3e80204d39
Leaving Monitored Scope (Request(POST:http://wssppoc:5000/_vti_bin/MenuService.svc/AddMenuItem)). Execution Time=3,9189 4af4519d-087a-c02e-e1ad-bb3e80204d39 ContentIntegrationEngine: Execution started, flow identifier is 4659ab80-0016-499b-a3ff-c1961ca797bd.
Does anyone have any idea what could be wrong?
Sam answer is leading to the proper solution. Which is using:
[System.Text.Encoding]::UTF8.GetBytes()
Working code is:
$MenuItem = New-Object -TypeName PSObject
$MenuItem | Add-Member -MemberType NoteProperty -Name LinkEN -Value $pageLink
$MenuItem | Add-Member -MemberType NoteProperty -Name LinkFR -Value $pageLink
$MenuItem | Add-Member -MemberType NoteProperty -Name TitleEN -Value $pageParam.ListTitle;
$MenuItem | Add-Member -MemberType NoteProperty -Name TitleFR -Value $pageParam.ListTitle;
$body = #{ _menuItem = $MenuItem; _parent = $null } | ConvertTo-Json
$url = $url + "/_vti_bin/MenuService.svc/AddMenuItem"
$body = [System.Text.Encoding]::UTF8.GetBytes($body);
$result = Invoke-RestMethod -Uri $url -Method Post -UseDefaultCredentials -ContentType "application/json; charset=utf-8" -Body $body