Archive

Archive for the ‘Powershell’ Category

Raiders of the lost VM´s in Hyper-V 2012 Cluster nodes

October 4th, 2012 2 comments

Inspired by Indiana Jones, I have made a little Powershell function to search cluster nodes for VM´s that has not been cluster enabled. If you create a VM in the Hyper-V manager or the Hyper-V powershell cmdlets, the VM is not highly available by default, even if you added it to a SMB share.

What do I do then, I only use one parameter and that is the cluster name, from this I get the HA – enabled VM´s and then check them for all VM´s registered on the hosts. After this I do a comparison and get a list of the VM´s objects that are not Cluster enabled, this can be pipelined to Add-VMToCluster cmdlet (an alias for Add-ClusterVirtualMachineRole) and you are home safe :-) . Of course there might be situations where you want a VM to reside only on one cluster node and not be highly available, Guest Clustering is one case where this might be a reason to not add them to a cluster. And if the VM has the configuration and storage locally you wont be able to add it to the cluster anyway

I can easily with PowerShell get the VM´s that are already HA enabled, but with this command I do not get the other VM´s on the cluster nodes.


Get-VM -ClusterObject (Get-ClusterResource -Cluster hypcl30 | where ResourceType -eq "Virtual Machine")

So if I want to get only the VM´s not cluster enabled, here is the function

<#
.Synopsis
   This function search the hosts for VMs tha are not HA enabled
.DESCRIPTION
   This function lets you find what VM´s that is running on your hosts and not activated on the virtual machine role on the cluster
.EXAMPLE
   Get-VMNotInCluster -Cluster HVCL30
.Link
vniklas.djungeln.se

.Notes
    Author: Niklas Akerlund /20121004
#>
function Get-VMNotInCluster
{
    [CmdletBinding()]
    [OutputType([int])]
    Param
    (
        # Name of the Cluster
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        $Cluster
    )
    Process
    {
        $ClusterNodes = Get-ClusterNode -Cluster $Cluster
        $VMsInCluster = Get-VM -ClusterObject (Get-ClusterResource -Cluster $Cluster | where ResourceType -eq "Virtual Machine")
        $VMsTotal = Get-VM -ComputerName (Get-ClusterNode -Cluster $Cluster).Name
        $VMsNotInCluster =@()
       
        foreach ($VM in $VMsTotal){
                if($VMsInCluster -notcontains $VM){
                    $VMsNotInCluster += $VM
                }
        }
        $VMsNotInCluster
    }
}

And here is a screendump of it running

 

And here is when I enable so that all VM´s are Highly Available

Now I do not have so many VM´s in my test environment but in a production environment maybe you can see the potential of knowing that all VM´s on all Clustered nodes are made HA enabled.

flattr this!

Convert Hyper-V vhd to vhdx and back with PowerShell

October 2nd, 2012 No comments

To convert an vhd disk file to vhdx superduper format with PowerShell you just use the cmdlet Convert-VHD (this work only on hyper-v enabled machines). I read Virtual PC Guys post about how to do it in the GUI and wanted to make a small post about how to do it in powershell

Convert-VHD -Path \\win2012-dc01\vms\old.vhd -DestinationPath \\win2012-dc01\vms\new.vhdx -DeleteSource -ComputerName win2012-hv01

And to go back

Convert-VHD -Path \\win2012-dc01\vms\new.vhdx -DestinationPath \\win2012-dc01\vms\old.vhd -DeleteSource -ComputerName win2012-hv01

And here is a screendump

And if I for example want to convert a number of vhd´s

first I create 5 dummy files, of course in the real life you have some files that already are there ready to be converted.

1..5|%{New-VHD -Path .\vhd$_.vhd -SizeBytes 2GB}
Get-VHD -Path C:\vhds\* | %{Convert-VHD -Path $_.Path -DestinationPath ($_.Path + "x") -DeleteSource}

olala look

If you want to convert back to vhd format you need to be sure that it is not bigger than 2040 GB or it will fail. Good luck!

flattr this!

Categories: Hyper-V, Powershell, Virtualization, Win2012 Tags:

In Windows Hyper-V 2012, Move-VMStorage leaves folders behind

September 26th, 2012 No comments

I have tested a bit with the WinServ first in the early version 8 and then RC and now RTM, and what I thought was a bug that MS would fix before RTM seems to be still there.

What am I talking about then, well when you do a live or cold storage migration of a VM from for example your local storage to a SMB share either with the gui or preferebly with PowerShell, the built-in function leaves folders behind. And you can see where this is leading when moving a lot of VM´s, several VM´s folders retain with nothing inside and causing confusion!

As you can see on the screendump, the VM 2012 has been moved to another place but the folder still resides with no data in it, the subfolders are there but no disk files. And of course if I use the parameter -RetainVhdCopiesOnSource the folders should stay and also the configuration, vhd files :-)

So I have done a modified Move-VMStorage function that actually removes the source folder also after moving the VM.

Here is the powershell function and a screendump how it actually deletes the folder also, And as you can see, I check if the VM resides on a share or locally on a hyper-v host and then I use Invoke-Command to delete the folder on the host´s local volume. The script can run on any machine that has RSAT-Hyper-V Powershell tools installed and with an account that has rights to delete folders on the shares/hosts.

<#
.Synopsis
   An updated Move-VMStorage function
.DESCRIPTION
   To also remove the folder where the VM was residing this function also deletes the folder after moving the VM
.EXAMPLE
   Move-VMStorage2 -VM test -ComputerName HV02 -Path \\SMB-srv01\VMs\test
.NOTES
Author: Niklas Akerlund 20120926
#>
function Move-VMStorage2
{
    [CmdletBinding()]
    [OutputType([int])]
    Param
    (
        # A name of a VM or a VM object
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        $VM,
        # The name of the Hyper-V host
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        $ComputerName,
        # The path where the VM is going to be relocated to.
        [string]
        $Path
    )

        # Lets move and tidy the source folder
        $VM = Get-VM $VM -ComputerName $ComputerName
        Move-VMStorage -VM $VM -DestinationStoragePath $Path
        $VMOldPath = $VM.Path
        if ($VMOldPath.StartsWith("\\")) {
           Remove-Item -Path $VMOldPath -Recurse -Force
        }else{
            Invoke-Command -ComputerName $VM.ComputerName -ScriptBlock {Remove-Item -Path $Using:VMOldPath -Recurse -Force}
        }
        
}

Make sure that you always add the VM´s name to the path otherwise you will put the VM´s folders and files in the SMB folder directly and that will cause a mess and when you run my function it will try to clean that folder and all subfolders wich means all VM´s not running in that folder. Maybe I will add the control that It checks that the path to delete includes the VM name in a future update :-P

flattr this!

VM Monitoring with Windows 2012 Hyper-V failover cluster

September 6th, 2012 No comments

Today I have tested to set up VM monitoring and see how it works.

Kristian Nese has made a blog post about it, I wanted to do a bit more in Powershell, He also points out that it might in some scenarios not be so good to activate this on a VM that have multiple roles and I must agree in that. Another thing to say again is, this can only be done on Windows 2012 VM´s and they haft to either be in the same domain as the failover-cluster or in a trusted domain and also the cluster nodes must be able to connect to the VM over the network.

First for the cluster to be able to see the services that can be monitored I have to allow that in the VM´s firewall, I will also enable Remote Service Management to communicate so I can remotely check services with powershell.

via PS remote I enable the firewall rules

Set-NetFirewallRule -DisplayGroup "Virtual Machine Monitoring" -Enabled true
Set-NetFirewallRule -DisplayGroup "Remote Service Management" -Enabled true

And then I can do some magic in the powershell console to set up the VM monitoring, I use the -OverrideServiceRecoveryActions parameter so the VM monitoring will trigger first no matter what the service is configured to do in the recovery settings.

Get-Service apache -ComputerName pstest

Add-ClusterVMMonitoredItem -Service apache2.2 -OverrideServiceRecoveryActions -VirtualMachine powertest -Cluster hypclu3

Get-ClusterVMMonitoredItem -VirtualMachine powertest -Cluster hypclu3

And in the Cluster Manager it looks like this:

 

To test this I want to kill the service and for that I can use the Stop-Process, this can not be used in a cmdlet remote so I have to use remoting again so with the Invoke-Command I get the process and kill it :-)

Invoke-Command -ComputerName pstest -ScriptBlock {Get-Process httpd | Stop-Process -Force}

And after just a breath the server reboots :-)

And if I want to remove the monitored services from the VM in the cluster I just run this command:

Get-ClusterVMMonitoredItem -VirtualMachine powertest -Cluster hypclu3 | Remove-ClusterVMMonitoredItem

In this example I have used a third party open source software to show that the monitoring not only just works with Microsoft services. It is as I said in the beginning useful in the right circumstance.

flattr this!

Categories: Hyper-V, Powershell, Virtualization, Win2012 Tags:

Live Migrate VM´s in Hyper-V cluster from RC to RTM node

September 5th, 2012 No comments

Yesterday I upgraded a Hyper-V node in a cluster from RC to RTM, Now I want to live migrate VM´s from the RC node to the RTM and then upgrade that.

As you can see on this screendump I have one node with build number 8400 (RC) and one with 9200 (RTM)

As you can see on this next image I have two VM´s running on my old node and I want to migrate them. The VM´s reside on a SMB 3.0 share and the cluster have a quorum share

And how do I move the VM´s to the other node, yes with powershell also, what you can see is that the Move-ClusterVirtualMachineRole can take the VMid from the VM objects as pipelined input, and that is really nice.

So now I have live migrated over the VM´s from the old RC to the RTM server and I can continue to upgrade that node.

I get an error if I try to live migrate them back to the old node, the only way I can do that is when the VM´s are powered off.

 

flattr this!

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

August 24th, 2012 No comments

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

flattr this!

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

August 22nd, 2012 1 comment

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,`
            $WBEM_PARTIAL_WRITE_REP,$WBEM_WRITE_PROVIDER,$WBEM_REMOTE_ACCESS,`
            $READ_CONTROL,$WRITE_DAC
        $WBEM_RIGHTS_STRINGS = "Enable","MethodExecute","FullWrite","PartialWrite",`
            "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`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)"
    }
}
}

 

flattr this!

Categories: Automation, Powershell Tags:

Updated: Change to Powershell console in Windows 2012 Core

July 8th, 2012 No comments

This friday I wrote a post about how to change from CMD to powershell and also found Thomas Lee´s posts about it.

What i wrote in my post was that i found the registry key that holds the command prompt but could not alter it. Andrew Morgan had already done all in his post, he also describes how you do, the secret is to take the ownership of the registry part (otherwise it is TrustedInstaller that is owner and that was why I could not change anything), after you change owner you also want to add some permissions, cause Administrator has only read by default.

 

And why do I want to add an registry key under AlternateShell instead of the Shell registry key that holds Explorer.exe. One reason is that when I enable the GUI from Core mode I do not get the full GUI. So when I run Get-WindowsFeature *gui-shell* | Add-WindowsFeature and then reboot the server it looks like the following screendump:

As you can see in the following screendump and in Andrew´s post, I keep the CMD and just set an new registry key that has a higher value, but not as high as Explorer. Doing this I get the Powershell console when in Core and the Explorer when in Full GUI mode. When in full GUI mode I get a temporary Pagefile error though :-( , the system seems to work correctly.. I have tested the Get-WindowsFeature *gui-shell* | Add-WindowsFeature on an installation where I have not done any alternation with the shells and there the page file dialog appears also so this is caused by another issue that I will investigate in another blog post.

 

flattr this!

Categories: Automation, Powershell, Win2012 Tags:

Change to Powershell console in Windows 2012 Core instead of CMD

July 6th, 2012 No comments

In twitter yesterday I saw that there was a discussion about setting default shell to Powershell in Windows Core, Jeffery Hicks has done this in Windows 2008 R2 and I wanted to test if his little trick worked in Windows 2012 also (by the way, why is it not default by default in 2012 Core? )

I have my testmachine ready and so lets go

Interestingly, When I check the registry I got that my shell in the 2012 Core was Explorer.exe,

but when I got a bit deeper I could see under AlternativeShells that the CMD was there

I tried to edit that but got an error :-(

well lets try to exchange the explorer.exe in shell  to powershell.exe instead, I use the $env:userprofile to get the same directory as with cmd


$shell = "Powershell.exe -NoExit cd `$env:userprofile"
set-itemproperty "hklm:\software\microsoft\windows nt\currentversion\winlogon" shell $shell

And here you can see in my registry, notice that the $env:userprofile is still there thanks to the ` in the string variable and that means that when another user log in they will get their own directory:

 And when I try to log in again I get the Powershell Console by default and also in the “right” directory

Lets hope Microsoft set Powershell console as default also in the Core version when Windows 2012 becomes RTM :-)

Update: I did apparently not follow the tweets so far that I noticed that the PS master Thomas Lee already had made a blog about this, in his post I would just add this to get the users directory

Set-ItemProperty -Confirm  -Path $RegPath -Name Shell -Value 'PowerShell.exe -noExit -Command "$psversiontable; cd $env:userprofile"' 

flattr this!

Categories: Automation, Powershell, Win2012 Tags:

Clone VM on Win 2012 Hyper-V v3 when it is running using Powershell

June 19th, 2012 3 comments

I wanted to create a function that could help an IT Pro Admin with the task to create a clone of a running VM, Yes you can use the Export-VM cmdlet but then your VM must be turned off and in some cases, for example when you want to test a new release or patch on a production system but first test it in a safe environment and you are not allowed to stop the original VM.

The function exists in System Center Virtual Machine Manager but there it also must be turned off

So how do I do? I have made a PowerShell function that take a snapshot, copies the vhd files and creates a new VM, connect the vhd´s and network cards, configures the VM with number of processors, dynamic memory etc.

As the Snapshot merges the vhdx files when the VM is running in this new Hyper-V version I thought it was an sufficient way to solve that the VM actually was not writing to the .vhdx files when I copy them but into the .avhdx diff file.

This is a version 0.1 and yes it need some development but right now I do not have the time to make it supernice,

Here you can see it in action

And when It is finished It looks like this in the gui

And here is the PowerShell function

<#
.Synopsis
   Function to clone a running VM
.DESCRIPTION
   This function can be used to clone a running vm and connect the copied vhd´s and network
.EXAMPLE
   Clone-VM -VMName vmdisktest -VMCloneName vmdisktest-clone -Path c:\VMs
.EXAMPLE
   Clone-VM -VMName vmdisktest -VMCloneName vmdisktest-clone -Path c:\VMs -Switch Private
.Notes
Author: Niklas Akerlund
Date: 2012-06-19
#>
function Clone-VM
{
    [CmdletBinding()]
    [OutputType([int])]
    Param
    (
        # Name of the VM to be cloned
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        $VMName,
        [string] $VMCloneName,
        # Where to store the VM-clone 
        [string] $Path,
        # Set to another network switch
        [string] $Switch = " "    
    )

    # Get VM
    $VM = Get-VM $VMName
    $VHDs = Get-VMHardDiskDrive -VM $VM
    #$VHDPath = (Get-VMHardDiskDrive -VM $VM).Path
    $VHDClonePath = "$Path\$VMCloneName\Virtual Hard Disks\"
    
    # Create a new array of VHD info
    $VHDOrg = @()
    foreach ($VHD in $VHDs){
        $data = New-Object PSObject -property @{
            VHDName = (Get-Item $VHD.Path).Name
            VHDPath = $VHD.Path
            ControllerType = $VHD.ControllerType
            ControllerNumber = $VHD.ControllerNumber
            ControllerLocation = $VHD.ControllerLocation

        }
        $VHDOrg +=$data
    }

    $VMNics = Get-VMNetworkAdapter -VM $VM

    # Take snapshot 
    $VM | Checkpoint-VM 

    New-Item -ItemType directory -Path $VHDClonePath

    New-VM -Name $VMCloneName -Path $Path -NoVHD -MemoryStartupBytes $VM.MemoryStartup -BootDevice IDE
    $VMClone = Get-VM -Name $VMCloneName
    Get-VMNetworkAdapter -VMName $VMCloneName | Remove-VMNetworkAdapter
   
    # Configure VM-Clon
    if ($VM.DynamicMemoryEnabled){
        Set-VM -VMName $VMCloneName -ProcessorCount $VM.ProcessorCount -DynamicMemory -MemoryMinimumBytes $VM.MemoryMinimum -MemoryMaximumBytes $VM.MemoryMaximum
    }else{
        Set-VM -VMName $VMCloneName -ProcessorCount $VM.ProcessorCount -StaticMemory
    }

    # Add all network cards
    foreach ($VMNic in $VMNics){
        if ($Switch -eq " "){
            Add-VMNetworkAdapter -VMName $VMCloneName -SwitchName $VMNic.SwitchName -IsLegacy $VMNic.IsLegacy
        }else{
            Add-VMNetworkAdapter -VMName $VMCloneName -SwitchName $Switch -IsLegacy $VMNic.IsLegacy
        }
    }

    # Copy all VHDs
    foreach ($VHDcopy in $VHDOrg){
        
        $Dest = $VHDClonePath + $VHDcopy.VHDName
        Copy-Item -Path $VHDcopy.VHDPath -Destination $Dest
        Add-VMHardDiskDrive -VMName $VMCloneName -ControllerType $VHDCopy.ControllerType -ControllerLocation $VHDCopy.ControllerLocation  -ControllerNumber $VHDCopy.ControllerNumber -Path $Dest   
    }

    # Remove snapshot 
    $VM | Remove-VMSnapshot

}

Good luck in testing, but do take in consideration that this is an copy of the running VM so do not start it at the same time on the same network or you will get IP collision etc, as you can see above I have added an -Switch parameter that you can use to set the network cards of the VM to be connected on another switch to avoid any problems.. :-)

flattr this!