Using PowerShell to get/start VM´s from Hyper-V servers in Active Directory

Of course I would rather have all my hosts in my System Center VMM 2012 SP1 but if I do not, or I just as an ITPro admin want to check if there are some new Hyper-V servers out there in my Active Directory and if they have VM´s on them I can use PowerShell.

I have looked at Ravikanth´s blog and done some modifications, also I am using the Win2012 and Powershell v3.

I can with the following powershell cmd get all hyper-v virtual machines in my Active Directory (of some reason the VMware machines do not get registered in the AD as virtual machines). With powershell version 3 it autoloads the Active Directory Module, this requires though that I have the feature RSAT-AD-PowerShell installed, easily done with Get-WindowsFeature RSAT-AD-PowerShell | Add-WindowsFeature.

Get-ADObject -Filter 'Name -like "*Virtual*"' | %{$_.DistinguishedName.Split(",")[1].replace("CN=","") }

I cannot do so much more with this information, what I want is to check a list of Hyper-V servers and also try to get the VM´s on them, sadly only win2012 servers can interact with the hyper-v powershell module and give that info, but hey whom has win 2008 r2 hyper-v servers 😉

This first query gives me all Hyper-V servers in the domain, 2008->2012

Get-ADObject -Filter 'Name -like "*Hyper-V"' | %{$_.DistinguishedName.Split(",")[1].replace("CN=","") }

This next query uses that info and asks the Hyper-V servers for their VM´s

Get-ADObject -Filter 'Name -like "*Hyper-V"' | %{$_.DistinguishedName.Split(",")[1].replace("CN=","") } | %{get-vm -ComputerName $_ -ErrorAction SilentlyContinue} | ft ComputerName,Name,State,Uptime -AutoSize

I can also use this to do things with the VM´s, like starting or stopping, observe that i just want to start the VM´s that start with the name test*.

Get-ADObject -Filter 'Name -like "*Hyper-V"' | %{$_.DistinguishedName.Split(",")[1].replace("CN=","") } | %{get-vm test* -ComputerName $_ -ErrorAction SilentlyContinue} | Start-VM

Set up non-admin account to access WMI and performance data remotely with PowerShell

I am working in a project that want to get performance data from remote servers, this with WMI, the servers belong to an Active Directory but the user collecting is not allowed to be an administrator (As an Administrator this is easy because then you already are in control and in the right groups)

So How do I fix this with preferably PowerShell, Scripting Guy has made a post about how to add users to local groups and Steve Lee on the MSDN WMI Blog has made a post how to add permissions on WMI  so I have combined them and got the solution I want.

The groups I am interested in on the remote computer are  “Distributed COM Users”,”Performance Monitor Users”, to be able to connect and get monitoring data

To get it from the WMI interface I need some permissions there, I only grant read permissions on the root/CIMv2 subtree.

And here is the Powershell that does it, In the screendump you can see that I can use a foreach loop to set the permissions on several computers

# Create Permissions for non-admin user on remote computers
#
# Niklas Akerlund / 2012-08-22
Param ([switch]$add,
	[switch]$remove,
    $ComputerName = "vc",
    $UserName = "olle",
    $DomainName = "vniklas")

# add functions to call
. .\Set-UserLocalGroup.ps1
. .\Set-WmiNamespaceSecurity.ps1

$LocalGroups = "Distributed COM Users","Performance Monitor Users"

if ($add){
	$LocalGroups | %{Set-UserLocalGroup -Computer $ComputerName -Group $_ -Domain $DomainName -User $UserName -add}
	Set-WMINamespaceSecurity root/CIMv2 add "$DomainName\$UserName" Enable,MethodExecute,ReadSecurity,RemoteAccess -computer $ComputerName
} elseif($remove) {
	$LocalGroups | %{Set-UserLocalGroup -Computer $ComputerName -Group $_ -Domain $DomainName -User $UserName -remove}
	Set-WMINamespaceSecurity root/cimv2 delete "$DomainName\$UserName" -computer $ComputerName
}

To check that I can get the performance data I can use the powershell cmdlet Get-WMIObject , as you can see in the screendump, in the first test I do not have the permissions set but in the second it works..

Here are the functions

Set-UserLocalGroup

# Add/Remove user from local group 		
#
# Niklas Akerlund/Most code from Scriptingguy Blog http://blogs.technet.com/b/heyscriptingguy/archive/2010/08/19/use-powershell-to-add-domain-users-to-a-local-group.aspx

Function Set-UserLocalGroup 
{ 
	[cmdletBinding()] 
	Param( 
	[Parameter(Mandatory=$True)] 
	[string]$Computer, 
	[Parameter(Mandatory=$True)] 
	[string]$Group, 
	[Parameter(Mandatory=$True)] 
	[string]$Domain, 
	[Parameter(Mandatory=$True)] 
	[string]$User,
	[switch]$add,
	[switch]$remove 
	)
	
	 
	$de = [ADSI]"WinNT://$Computer/$Group,group" 
	if($add){
		$de.psbase.Invoke("Add",([ADSI]"WinNT://$Domain/$User").path) 
	} elseif ($remove){
		$de.psbase.Invoke("Remove",([ADSI]"WinNT://$Domain/$User").path)
	}
	
} 

Set-WmiNamespaceSecurity, there was an error in the original code that I have corrected based on the comments, this error caused the function to always run on the local computer instead of the remote when using the -computer parameter.

# Copyright (c) Microsoft Corporation.  All rights reserved. 
# For personal use only.  Provided AS IS and WITH ALL FAULTS.
 
# Set-WmiNamespaceSecurity.ps1
# Example: Set-WmiNamespaceSecurity root/cimv2 add steve Enable,RemoteAccess
Function Set-WmiNamespaceSecurity {
 
Param ( [parameter(Mandatory=$true,Position=0)][string] $namespace,
    [parameter(Mandatory=$true,Position=1)][string] $operation,
    [parameter(Mandatory=$true,Position=2)][string] $account,
    [parameter(Position=3)][string[]] $permissions = $null,
    [bool] $allowInherit = $false,
    [bool] $deny = $false,
    [string] $computer = ".",
    [System.Management.Automation.PSCredential] $credential = $null)
   
Process {
    $ErrorActionPreference = "Stop"
 
    Function Get-AccessMaskFromPermission($permissions) {
        $WBEM_ENABLE            = 1
                $WBEM_METHOD_EXECUTE = 2
                $WBEM_FULL_WRITE_REP   = 4
                $WBEM_PARTIAL_WRITE_REP              = 8
                $WBEM_WRITE_PROVIDER   = 0x10
                $WBEM_REMOTE_ACCESS    = 0x20
                $WBEM_RIGHT_SUBSCRIBE = 0x40
                $WBEM_RIGHT_PUBLISH      = 0x80
        	$READ_CONTROL = 0x20000
        	$WRITE_DAC = 0x40000
       
        $WBEM_RIGHTS_FLAGS = $WBEM_ENABLE,$WBEM_METHOD_EXECUTE,$WBEM_FULL_WRITE_REP,<code>
            $WBEM_PARTIAL_WRITE_REP,$WBEM_WRITE_PROVIDER,$WBEM_REMOTE_ACCESS,</code>
            $READ_CONTROL,$WRITE_DAC
        $WBEM_RIGHTS_STRINGS = "Enable","MethodExecute","FullWrite","PartialWrite",<code>
            "ProviderWrite","RemoteAccess","ReadSecurity","WriteSecurity"
 
        $permissionTable = @{}
 
        for ($i = 0; $i -lt $WBEM_RIGHTS_FLAGS.Length; $i++) {
            $permissionTable.Add($WBEM_RIGHTS_STRINGS[$i].ToLower(), $WBEM_RIGHTS_FLAGS[$i])
        }
       
        $accessMask = 0
 
        foreach ($permission in $permissions) {
            if (-not $permissionTable.ContainsKey($permission.ToLower())) {
                throw "Unknown permission: $permission</code>nValid permissions: $($permissionTable.Keys)"
            }
            $accessMask += $permissionTable[$permission.ToLower()]
        }
       
        $accessMask
    }
 
    if ($PSBoundParameters.ContainsKey("Credential")) {
        $remoteparams = @{ComputerName=$computer;Credential=$credential}
    } else {
        $remoteparams = @{ComputerName=$computerName}
    }
       
    $invokeparams = @{Namespace=$namespace;Path="__systemsecurity=@"} + $remoteParams
 
    $output = Invoke-WmiMethod @invokeparams -Name GetSecurityDescriptor
    if ($output.ReturnValue -ne 0) {
        throw "GetSecurityDescriptor failed: $($output.ReturnValue)"
    }
 
    $acl = $output.Descriptor
    $OBJECT_INHERIT_ACE_FLAG = 0x1
    $CONTAINER_INHERIT_ACE_FLAG = 0x2
 
    $computerName = (Get-WmiObject @remoteparams Win32_ComputerSystem).Name
   
    if ($account.Contains('\')) {
        $domainaccount = $account.Split('\')
        $domain = $domainaccount[0]
        if (($domain -eq ".") -or ($domain -eq "BUILTIN")) {
            $domain = $computerName
        }
        $accountname = $domainaccount[1]
    } elseif ($account.Contains('@')) {
        $domainaccount = $account.Split('@')
        $domain = $domainaccount[1].Split('.')[0]
        $accountname = $domainaccount[0]
    } else {
        $domain = $computerName
        $accountname = $account
    }
 
    $getparams = @{Class="Win32_Account";Filter="Domain='$domain' and Name='$accountname'"}
 
    $win32account = Get-WmiObject @getparams
 
    if ($win32account -eq $null) {
        throw "Account was not found: $account"
    }
 
    switch ($operation) {
        "add" {
            if ($permissions -eq $null) {
                throw "-Permissions must be specified for an add operation"
            }
            $accessMask = Get-AccessMaskFromPermission($permissions)
   
            $ace = (New-Object System.Management.ManagementClass("win32_Ace")).CreateInstance()
            $ace.AccessMask = $accessMask
            if ($allowInherit) {
                $ace.AceFlags = $OBJECT_INHERIT_ACE_FLAG + $CONTAINER_INHERIT_ACE_FLAG
            } else {
                $ace.AceFlags = 0
            }
                       
            $trustee = (New-Object System.Management.ManagementClass("win32_Trustee")).CreateInstance()
            $trustee.SidString = $win32account.Sid
            $ace.Trustee = $trustee
           
            $ACCESS_ALLOWED_ACE_TYPE = 0x0
            $ACCESS_DENIED_ACE_TYPE = 0x1
 
            if ($deny) {
                $ace.AceType = $ACCESS_DENIED_ACE_TYPE
            } else {
                $ace.AceType = $ACCESS_ALLOWED_ACE_TYPE
            }
 
            $acl.DACL += $ace.psobject.immediateBaseObject
	    
        }
       
        "delete" {
            if ($permissions -ne $null) {
                throw "Permissions cannot be specified for a delete operation"
            }
       
            [System.Management.ManagementBaseObject[]]$newDACL = @()
            foreach ($ace in $acl.DACL) {
                if ($ace.Trustee.SidString -ne $win32account.Sid) {
                    $newDACL += $ace.psobject.immediateBaseObject
                }
            }
 
            $acl.DACL = $newDACL.psobject.immediateBaseObject
        }
       
        default {
            throw "Unknown operation: $operation`nAllowed operations: add delete"
        }
    }
 
    $setparams = @{Name="SetSecurityDescriptor";ArgumentList=$acl.psobject.immediateBaseObject} + $invokeParams
 
    $output = Invoke-WmiMethod @setparams
    if ($output.ReturnValue -ne 0) {
        throw "SetSecurityDescriptor failed: $($output.ReturnValue)"
    }
}
}

 

Activate my Win8 Enterprise with slmgr.vbs

I have installed and configured my Win 8 Enterprise but did not enter any key when I was deploying. Now when I wanted to do some customization I saw this.

Well ok, I tried to activate but got this error,

 In the GUI there was no way like in win 7 to enter a new product key, so I had to use the slmgr.vbs script. Why have not Microsoft developed this function in powershell instead?

What you will realize is that you need to run it in an elevated powershell or cmd console.

slmgr.vbs -dli  <- this shows the product key that you have now

slmgr.vbs -ipk 0000-0000-0000-0000  <- Installs the product key

Now I have an activated Windows 8 client with a valid license  🙂

At last now I am officially an MCSE:Private Cloud

At the MMS 2012 I had the opportunity to write the beta exam 71-246, Monitoring and Operating a Private Cloud with System Center 2012. This exam was released during the summer as 70-246

As my earlier post announced I could see on the Prometric site that I passed, but the MCP site did not reflect this and I waited quite a while but nothing happend, so I sent in a support request and now after a week I got a mail that they have solved the transfer from Prometric.

Here is a screendump of my certification transcript 🙂 and watch the date of achievement..

 

Good luck you other guys in achieving MCSE Private Cloud!

New free tool vOPS Server Explorer 5.1 from vKernel

There is a new version of the vKernel Server Explorer that has a Environment Explorer view, this can handle multiple hypervisors (current support is Hyper-V, VMware vSphere and RHEV). The tool gives you as an administrator or consultant the insight in the environment and can give you answers if there are some issues and if you are running out of capacity.

The software is packed as an OVF and imported into your vSphere environment, as the vOPS server Readme text file describes there will also be an Hyper-V appliance that will be importable into an Hyper-V host and that with an collector for Hyper-V, do not know when that will be released though.

I have imported it with PowerCLI and then edited the vCPU to 2 instead of 4 and memory to 4 GB instead of 8 because my lab machines has limited resourses, It is really easy to deploy as you can see.

After import and configure I start it and then use a browser to the IP adress and configure, with this current version I can only add vSphere vCenter and RHEV

To add a vCenter I have to set the vCenter statistic level to at least 2 on all intervals

When that is done I successfully add the vCenter to the Server Explorer

With this you get three free tools in one, Environment Explorer, vScope Explorer and SearchMyVM Explorer that all can help you get the initial info you need. and If you want you can activate a free vOPS trial to extend the functionality.

In my small test/lab environment it looks like this

Download the Server Explorer and test yourself -> vOPS Server Explorer 5.1