I need to execute PowerShell on VMs from an Azure Automation Runbook, akin to a WinRm execution/PowerShell Remoting.
I have created an Azure Runbook through the Azure Automation GUI, and am trying to run a script that works perfectly against physical and virtual machines to get key system information and ports. I am able to authenticate in Azure and it appears that I can execute some aspects of the script (unless it's only running against the Azure Automation Worker) via the Azure Runbook, such as getting the installed PowerShell Version of the targeted VMs using: $PSVersionTable.PSVersion so I am not having issues with security/access from what I can tell.
However, several other components fail as follows, and I don't know if I need to import a Module to Azure Automation and if so, which ones. Or if this is failing because it is running against the Worker and not the VMs.
Here are some of the code snippets I am running:
$computerSystem = Get-CimInstance Win32_ComputerSystem
"CPU: " + $computerCPU.Name
Get-WmiObject -Class Win32_LogicalDisk |
Where-Object {$_.DriveType -ne 5} |
Sort-Object -Property Name |
Select-Object Name, VolumeName, FileSystem, Description, `
#{"Label"="DiskSize(GB)";"Expression"={"{0:N}" -f ($_.Size/1GB) -as [float]}}, `
#{"Label"="FreeSpace(GB)";"Expression"={"{0:N}" -f ($_.FreeSpace/1GB) -as [float]}}, `
#{"Label"="%Free";"Expression"={"{0:N}" -f ($_.FreeSpace/$_.Size*100) -as [float]}} |
Format-Table -AutoSize
Get-NetAdapter -Name "*" | Format-Table
Get-NetOffloadGlobalSetting | Format-List
Test-NetConnection -Port 80
Here are the error messages, which I strongly suspect are EITHER due to missing PowerShell Modules that I need to upload, but am unsure where to find these OR is this a situation where I am not targeting the VM correctly and instead running this against the AZ Host? (if so, any good examples of how to target a single VM):
Get-CimInstance : The specified service does not exist as an installed
service.
Get-WmiObject : The specified service does not exist as an installed
service.
Get-NetAdapter : The term 'Get-NetAdapter' is not recognized as the
name of a cmdlet, function, script file, or operable program.
Get-NetOffloadGlobalSetting : The term 'Get-NetOffloadGlobalSetting'
is not recognized as the name of a cmdlet, function, script file, or
operable program.
Test-NetConnection : The term 'Test-NetConnection' is not recognized
as the name of a cmdlet, function, script file, or operable program.
If it is an issue with targeting the VM properly, I need some guidance. I suspect that I am targeting the Worker running the Runbooks and not the actual VMs. I am using the RunAs account/the new Azure Automation security methods (not classic) so I don't believe certificates come into play. Here is how I am trying to target the VM (which I suspect is incorrect/should be changed):
$Resources = Get-AzureRmResource -ResourceType "Microsoft.Compute/virtualMachines" -ResourceGroupName "MyTestGroup" -ResourceName "MyTestVM"
ForEach ($Resource in $Resources)
{
# PowerShell Code from Above here
}
UPDATE 1:
Now that we have determined that I am not targeting the VM properly, I tried Joe's recommendation, but when I try to run the following I get an error on the WinRm. I found the Connect-AzureVM.ps1, but am unsure if this is old or aligns to the newer RunAs Connection I am using. Here is my current script that attempts to connect to the VM and Invoke PowerShell.
param(
[parameter(Mandatory=$true)][String] 'https://myvmname.eastus.cloudapp.azure.com:5986,
[parameter(Mandatory=$true)][String] 'MyVMName'
)
$connectionName = "AzureRunAsConnection"
try
{
# Get the connection "AzureRunAsConnection "
$servicePrincipalConnection=Get-AutomationConnection -Name $connectionName
"Logging in to Azure..."
Add-AzureRmAccount `
-ServicePrincipal `
-TenantId $servicePrincipalConnection.TenantId `
-ApplicationId $servicePrincipalConnection.ApplicationId `
-CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
}
catch {
if (!$servicePrincipalConnection)
{
$ErrorMessage = "Connection $connectionName not found."
throw $ErrorMessage
} else{
Write-Error -Message $_.Exception
throw $_.Exception
}
}
# Get credentials to Azure VM
$Credential = Get-AutomationPSCredential -Name $VMCredentialName
Invoke-Command -ConnectionUri $Uri -Credential $Credential -ScriptBlock {
# My PowerShell Here
}
This is the error the script produces. I suspect its because I need to import/create a WinRM certificate on the VM I am targeting, but unsure if the Connect-AzureVM.ps1 is the right script to use or if there is another/more updated method to use for WinRM access:
[myvmname.eastus.cloudapp.azure.com] Connecting to remote server
myvmname.eastus.cloudapp.azure.com failed with the following error
message : WinRM cannot complete the operation. Verify that the
specified computer name is valid, that the computer is accessible
over the network, and that a firewall exception for the WinRM service
is enabled and allows access from this computer. By default, the
WinRM firewall exception for public profiles limits access to remote
computers within the same local subnet. For more information, see the
about_Remote_Troubleshooting Help topic.
+ CategoryInfo : OpenError: (myvmname.eastus.cloudapp.azure.com:String) [],
PSRemotingTransportException
+ FullyQualifiedErrorId : WinRMOperationTimeout,PSSessionStateBroken
Run this inside the VM at an elevated prompt
https://gist.github.com/jeffpatton1971/2321f0db8025e48ad8ec13c243153045
From inside your Runbook do whatever you normally to wire it up but create some session options to pass along your invoke-command.
$SessionOption = New-PSSessionOption -SkipCACheck -SkipCNCheck
Invoke-Command -ComputerName $VMname -Credential $Credential -UseSSL -SessionOption $SessionOption -ScriptBlock { }
You need to either add the VMs you want to run these scripts on as Azure Automation hybrid workers, so that you can target the script to run on them, or you need to, from your runbook running on Azure Automation's own workers, remote into each VM and from within the remoting block run the commands.
For the former, see: https://learn.microsoft.com/en-us/azure/automation/automation-hybrid-runbook-worker
For the ladder:
Invoke-Command -ConnectionUri $Uri -Credential $Credential -ScriptBlock {
$computerSystem = Get-CimInstance Win32_ComputerSystem
"CPU: " + $computerCPU.Name
Get-WmiObject -Class Win32_LogicalDisk |
Where-Object {$_.DriveType -ne 5} |
Sort-Object -Property Name |
Select-Object Name, VolumeName, FileSystem, Description, `
#{"Label"="DiskSize(GB)";"Expression"={"{0:N}" -f ($_.Size/1GB) -as [float]}}, `
#{"Label"="FreeSpace(GB)";"Expression"={"{0:N}" -f ($_.FreeSpace/1GB) -as [float]}}, `
#{"Label"="%Free";"Expression"={"{0:N}" -f ($_.FreeSpace/$_.Size*100) -as [float]}} |
Format-Table -AutoSize
Get-NetAdapter -Name "*" | Format-Table
Get-NetOffloadGlobalSetting | Format-List
Test-NetConnection -Port 80
}
Related
I'm trying to install Windows Updates on a Remote Computer with this command:
$InstallSplat = #{
AcceptAll = $true
SendReport = $true
IgnoreReboot = if ($Reboot) { $false } else { $true }
PSWUSettings = #{
SmtpServer = "my mail server"
From = "myfrom <myfrom#myfrom.com>"
To = "myto <myto#myto.com>"
Port = 25
}
}
Invoke-Command -ComputerName $_ -Credential $cred -AsJob -ArgumentList $InstallSplat -ScriptBlock {
param([hashtable]$InstallSplat)
Import-Module PSWindowsUpdate
Install-WindowsUpdate #InstallSplat
$Error | out-file C:\install\installwinupdate.log -Append
}
I pass a credential Object with domain admin privileges in $cred but I still always get this error
Install-WindowsUpdate : Access denied (Ausnahme von HRESULT: 0x80070005 (E_ACCESSDENIED)) In Zeile:4 Zeichen:25
+ Install-WindowsUpdate #InstallSplat
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-WindowsUpdate], UnauthorizedAccessException
+ FullyQualifiedErrorId : System.UnauthorizedAccessException,PSWindowsUpdate.GetWindowsUpdate
The Command Install-WindowsUpdate itself does not have a credential parameter I could use. The Command needs to run in an elevated PowerShell, but I use an elevated PowerShell when starting this command on my Computer.
I Also tried creating a New-PSSession with my $cred and run Invoke-Command -Session $session instead of Invoke-Command -ComputerName $_ with the same result.
Does anybody know what's happening here? Why do I get Access denied?
It can't have anything to do with passing the $InstallSplat because the same thing happens if I don't pass any parameter at all and write the parameters and their Values directly at the command instead of splatting.
The Problem was, that you can't Download or Install Updates on a machine from another remote machine. Here's a list what you can or can't do remotely when it comes to Windows Updates
The solution is, to create a scheduled task on each server you want to install updates from a remote script, and start that task.
luckily, when you use the PSWindowsUpdate module, you don't have to do that yourself, you can just use Invoke-WUJob (formerly Invoke-WUInstall) which does the trick for you.
I used it like so ($ServerData.Value contains a list of my Servers) and it works like a charm. It creates a scheduled task on each server, and runs them immediately, if you add the -RunNow Parameter.
invoke-WUJob -ComputerName $ServerData.Value -Script { Import-Module PSWindowsUpdate ; Install-WindowsUpdate -AcceptAll -SendReport -IgnoreReboot -PSWUSettings #{From='xy';Port=25;SmtpServer='xy';To='xy'} | Out-File C:\install\PSWindowsUpdateLog.txt -Append} -Confirm:$false -verbose -RunNow
Note that what you specify as a script block in -Script will be pasted to -Command " <here> " in your scheduled task, so you should work with ' inside -Script.
I'm trying to write an Azure Powershell Runbook that will start a VM, and then check if a windows service on the VM is running or not and start it.
I can get the VM started, but enumerating the services isn't working. I'm brand new on Azure Runbooks so I could be doing something wrong. I limited the below code to only the Get-Service bit and not the VM starting.
# Returns strings with status messages
[OutputType([String])]
param (
[Parameter(Mandatory=$false)]
[String] $AzureConnectionAssetName = "AzureRunAsConnection",
[Parameter(Mandatory=$false)]
[String] $ResourceGroupName = ""
)
try {
# Connect to Azure using service principal auth
$ServicePrincipalConnection = Get-AutomationConnection -Name $AzureConnectionAssetName
Write-Output "Logging in to Azure..."
$Null = Add-AzureRmAccount `
-ServicePrincipal `
-TenantId $ServicePrincipalConnection.TenantId `
-ApplicationId $ServicePrincipalConnection.ApplicationId `
-CertificateThumbprint $ServicePrincipalConnection.CertificateThumbprint
}
catch {
if(!$ServicePrincipalConnection) {
throw "Connection $AzureConnectionAssetName not found."
}
else {
throw $_.Exception
}
}
# If there is a specific resource group, then get all VMs in the resource group,
# otherwise get all VMs in the subscription.
if ($ResourceGroupName) {
$VMs = Get-AzureRmVM -ResourceGroupName $ResourceGroupName
}
else {
$VMs = Get-AzureRmVM
}
# Try and enumerate the VM's services
foreach ($VM in $VMs) {
Write-Output "Listing all services..."
Write-Output ("VM: {0}" -f $VM.Name)
$VM | Get-Service | Format-Table -AutoSize
Write-Output "Listing alternative method..."
Get-Service -ComputerName $VM.Name | Format-Table -AutoSize
Write-Output "Finished listing..."
}
And the output is this:
Logging in to Azure...
Listing all services...
VM: demo-0
Listing alternative method...
Finished listing...
Well, first of all, Starting VM is asynchronous, so you need to wait for the VM to actually boot, and the Get-Service wouldn't work anyway, as to get the services from a VM you need to authenticate against that VM, so either user PSsessions or invoke-command, something like that. Just look on how to remote into servers with powershell or how to issue powershell command to remote PC's. This case is nothing different. And it has nothing to do with how Azure Automation works.
When you are running Azure Automation runbooks, you have the choice of the azure queue or creating a hybrid worker. The azure queue is good for many processes, but it will not have access to the VMs directly to run commands such as get-service.
To expand on #4c74356b41 answer, you will need to use remote powershell to execute the command (using New-PSSession) but you will also have to ensure that those commands are running on an Azure Automation Hybrid Worker
In the comments below, you asked about credentials. You can set credentials in the Azure Automation account and then have them used by your script when creating a new session. See this link
You could try to use the following cmdlets. It works for me.
# Try and enumerate the VM's services
foreach ($VM in $VMs) {
Write-Output "Listing all services..."
Write-Output ("VM: {0}" -f $VM.Name)
$ResourceGroupName=$VM.ResourceGroupName
$Name=$VM.Name
$status=(Get-AzureRmVM -ResourceGroupName $ResourceGroupName -Name $Name -Status).Statuses[1].code
if($status -like "PowerState/deallocated")
{
Write-Output "VM is stopped, starting VM"
if (Start-AzureRmVm -ResourceGroupName $ResourceGroupName -Name $Name)
{
Write-Output "Start VM successfuly"
}else
{
Write-Output "Start VM failed"
break
}
}
##get VM's Public IP
$nicName = ($VM.NetworkInterfaceIDs[0] -split '/')[-1]
$pip = (Get-AzureRmNetworkInterface -Name $nicName -ResourceGroupName $ResourceGroupName).IpConfigurations.publicipaddress.id
$PublicIP=(Get-AzureRmPublicIpAddress -ResourceGroupName shui -Name ($pip -split '/')[-1]).IpAddress
$Uri="http://$($PublicIP):5986"
Write-Output "Get ConnectionUri $Uri"
##get Credential from assets
$shui=Get-AutomationPSCredential -Name 'shui'
$session=New-PSSession -ConnectionUri $Uri -Credential $shui -SessionOption (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck)
$result=Invoke-Command -Session $session -ScriptBlock {ipconfig}
Write-Output "$result"
Write-Output "Finished listing..."
}
Before you use this script. Firstly,you should open ports 5896 on your VM's firewall and NSG, you could check as the following link. Please ensure you could telnet IP 5986 on your local PC.
You should import AzureRM.Network modules to your Automation Account. More information about how to import Modules please refer to this link.
3.Store your VMs's passord to Runbook, you could refer to this link. When you want to use the credentials, you could use the script below:
##get Credential from assets
$shui=Get-AutomationPSCredential -Name 'shui'
$session=New-PSSession -ConnectionUri $Uri -Credential $shui -SessionOption (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck)
I'm stumped by this issue.
I've written a powershell script which I'm trying to use to import a GPO across multiple domains and then link it with new-gplink. I've made sure all servers have GP Powershell module installed and it's been working pretty well so far, however the issue I'm running into is that on some servers my command works fine on others I get the error, on the last step I'm getting an operations error one of my invoke-commands. Other commands work on the same server with invoke-command such as get-service, or even the import-GPO command that I use.
The error in question:
An operations error occurred. (Exception from HRESULT: 0x80072020)
+ CategoryInfo : NotSpecified: (:) [New-GPLink], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException,Microsoft.GroupPolicy.Commands.NewGPLinkCommand
+ PSComputerName : 10.0.0.10
The command:
Invoke-Command -ComputerName $serverip -scriptblock {New-GPLink -Name "GPO" -Target $args[0]} -ArgumentList $oupath -credential $cred
I've tried every version of this command I can imagine. without [0], without argument list, just using the server ip and replacing the target with the OU path and I still get the same error, such as below.
Invoke-Command -ComputerName $serverip -scriptblock {New-GPLink -Name "GPOName" -Target ou=users,ou=site,ou=domain,dc=server,dc=com} -ArgumentList $oupath -credential $cred
The way I have it working is a .csv with the server info, it gets imported into a foreach loop and then fed into the script. I have it grab credentials and feed through. I know everything else is working because my invoke-command to import the GPO worked, all servers I ran to successfully imported the GPO. I also know my OU paths are correct because I use them locally with another script to place computers where I want them. a sample line in the csv would be something like
servername, 10.0.0.10, domain.com, OU=user,OU=site,DC=domain,DC=com
I've also ran the command locally and get a similar error:
PS> New-GPLink -Name "GPO" -Target "ou=users,ou=Site,dc=domain,dc=com"
New-GPLink : A referral was returned from the server.
At line:1 char:1
+ New-GPLink -Name "GPO" -Target "ou=users,ou=site,dc=domain,d ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [New-GPLink], DirectoryServicesCOMException
+ FullyQualifiedErrorId : System.DirectoryServices.DirectoryServicesCOMException,Microsoft.GroupPolicy.Commands.NewGPLinkCommand
Please let me know if there are additional question or if you need additional info. I'm completely stumped by this issue and I appreciate any help you can provide. Thanks in advance.
Edit: All of my servers are at least 2008 R2 and are using powershell version 3,0,1,1
PS> $psversiontable.psversion
Major Minor Build Revision
----- ----- ----- --------
3 0 -1 -1
You need to specify a the domain in which your trying to apply the GPO, as well as a Domain Controller from the domain in question with the -Domain and -Server parameters respectively:
$OU = "ou=users,ou=Site,dc=domain,dc=com"
New-GPLink -Name "GPO" -Target $OU -Server "domain.com" -Domain "domain.com"
Instead of just using the domain name though, the proper way to do this, is to actually locate a Domain Controller, like so:
$DC = Get-ADDomainController -Discover -DomainName "domain.com" |Select -ExpandProperty HostName
New-GPLink -Name "GPO" -Target $OU -Server $DC -Domain "domain.tld"
Or in an environment where Get-ADDomainController is not available, you can emulate the DCLocator (aka. the underlying high-availability design of AD DS) behavior with .NET:
$DomainFqdn = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$dctx = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext -ArgumentList "Domain",$DomainFqdn
$DomainController = $[System.DirectoryServices.ActiveDirectory.DomainController]::FindOne($dctx)
New-GPLink -Name "GPO" -Target $OU -Server $DomainController.Name -Domain $DomainFqdn
I have the following powercli script to add a local account to an esx host.
With all my research I believe this should work but when I run it I get this error.
New-VIRole : 3/04/2014 9:23:49 a.m. New-VIRole The specified privileges are from a different server.
At .\test.ps1:18 char:11
+ New-VIRole <<<< -Name "CIM Only" -Privilege "CIM interaction"
+ CategoryInfo : InvalidArgument: (VMware.VimAutom...ent.Privilege[]:Privilege[]) [New-VIRole], VimException
+ FullyQualifiedErrorId : Core_NewVIRole_DoWork_PrivilegeFromDifferentServerSpecified,VMware.VimAutomation.ViCore.Cmdlets.Commands.PermissionManagement.NewVIRole`
Here is the script
## As usual, load needed PowerCLI cmdlets
asnp VMware.VimAutomation.Core -ErrorAction SilentlyContinue
# Define the ESXi server
$server = "servername"
#Connect to ESXi server
Connect-VIServer -Server $server -user user -password password
#Create a new role CIM, with the only needed privilege assigned to it
New-VIRole -Name "CIM Only" -Privilege "CIM interaction"
#Create the cimuser account, assign it to root group, and deny it shell access
New-VMHostAccount -Id cimuser -Password password -UserAccount:$true -AssignGroups root -GrantShellAccess:$false
#Assign the role CIM to the newly created cimuser account
New-VIPermission -Entity $server -Principal cimuser -Role "CIM Only"
#Disconnect from ESXi server
Disconnect-VIServer -Server $server -Confirm:$false
Any help greatly appreciated.
Try this:
New-VIRole -Name "CIM Only" -Privilege ( Get-VIPrivilege "CIM interaction" )
What esxi version are you running?
As part of my script, I have the following code inside a powershell module.
Set-Service -Name $ServiceName -ComputerName $ComputerName -Status $ServiceStatus -Confirm:$false -Verbose;
When this runs in the script I get the following error
Set-Service : Service 'Service Name' cannot be configured due to the following error: The specified service does not exist as an installed service
But then when running manually as part of my testing the command runs without error.
Set-Service -Name "Service Name" -ComputerName "machine name" -Status Stopped -Confirm:$false -Verbose;