Showing posts with label Hyper-V. Show all posts
Showing posts with label Hyper-V. Show all posts

Monday, June 2, 2014

Using Powershell to provision hyper-v VMs.

Hello folks,  I want to share with you this piece of script.
My goal is to make a provisioning script for VM that would take care of :

  • Configuring basic VM setup such as RAM, CPUs for each VM
  • Deploying multiple VM based on a sysprep master.
  • Configuring network and performing domain joins and AD setup
  • Running post sysprep provisioning tasks, such as role and feature installations.

The scripts are provided as is, without warranty, and may be improved in the future.
At the present moment, they are alpha quality.

Use at your own risk.

There are several pieces of scripts to make this all work :

#SERVER SCRIPT  : to be put on the provisioning hypervisor (createvm.ps1)
#CLIENT SCRIPT 1 : save it as canalwr2.ps1 under c:\scripts
#CLIENT SCRIPT 2 : save it as winrmconf.ps1 under c:\scripts
#CLIENT SCHEDULED TASK  : to be put imported on the sysprepped VM before sysprep
#DATA : Q:\securestring.txt : contains the credentials to access the VM through a remote powershell session (place it along the server script so it can be accessed by it)
#UNATTEND.XML : to be used as a sysprep argument to configure the syprep process properly, need some edits with Windows AIK. save it in c:\scripts
#SYSPREP COMMAND : to run sysprep


Basically you put the server script on the hypervisor, and configure the pathes so it can access your master VHDs
Before launching it, you will need  to sysprep a VM using the supplied unattend.xml and place the client scripts in c:\scripts and register a scheduled task using the administrator credentials and use the password that will be set by sysprep (so it will have a chance to run).
Once the VM is ready, sysprep it with the command supplied at the end of this article and export the master VHD you just created once the vm is stopped. It will be the reference image.

On the hypervisor, you can invoke the creation of  VMs with the following command :

createvm.ps1 <OS_MASTER_MONIKER> < NUM_OF_VMs> <RAM_IN_GB> <CORES>

it is a bit silly that you cannot customize the parameters to have different hardware setups for each VM but I am working on it...

enjoy, and comment.

#########################################################
#SERVER SCRIPT : (createvm.ps1)
#########################################################

 param (
    [string]$serverformula =  $(throw "-server formula is required.")
    #[string]$domainformula = $(throw "-domainformula is required."),
    #[string]$roleformula = $(throw "-roleformula is required.")
 )


$runINSTVM = {
             param($a, $b, $c, $d)

             #Function Instantiate-VM($OSName2, $NumOfVMs2, $RAM2, $Cores2)
             $OSName2 = $a
             $NumOfVMs2  =  $b
             $RAM2 = $c
             $Cores2 = $d

             echo $OSName2 | Out-File -FilePath "c:\Log$OSName2.txt" -Append -width 50

             $runFACVM = {
                        param($e, $f, $g, $h)

                        $OSName = $e
                        $VMIndex = $f
                        $RAM = $g
                        $Cores = $h
                        $i = 0

                        [string]$vmName = $OSName + "_" + $VMIndex;

                        Function GetVMInfo($PipeName)
                            {  
                                $HashVMInfo = @{}
                                do {
                                   
                                    $pipe=new-object System.IO.Pipes.NamedPipeClientStream -ArgumentList ".",$PipeName,1,0;
                                    echo "Created client side of $PipeName" |  Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50;

                                    $pipe.Connect();

                                    echo "Connected client side of $PipeName" |  Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50;

                                    $sr = new-object System.IO.StreamReader($pipe);

                                    echo "Connected stream reader" |  Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50;

                                    $IPaddr = $null;
                                    $CompN = $null;


                                    do {
   
                                            echo "entering main loop" |  Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50;
                                            $line = $sr.readline()
                                            $tmp1 = $line -match "#IPADDR=(\d+\.\d+\.\d+\.\d+)#"
                                            echo "matching ipaddr first" |  Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50;

                                            if ($tmp1)
                                                {
                                                $IPaddr = $matches[1];
                                                $line = $sr.readline()
                                                $tmp2 = $line -match "#COMPN=([\d\w\-_]+)#"
                                                echo "matching compn next" |  Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50;
                                                if ($tmp2)
                                                    {
                                                    $CompN = $matches[1];   
                                                    }
                                                }
                                            else
                                                {
                                                $tmp2 = $line -match "#COMPN=([\d\w\-_]+)#"
                                                if ($tmp2)
                                                    {
                                                    echo "matching compn first" |  Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50;;
                                                    $CompN = $matches[1];
                                                    $line = $sr.readline()
                                                    $tmp1 = $line -match "#IPADDR=(\d+\.\d+\.\d+\.\d+)#"
                                                    if ($tmp1)
                                                        {
                                                        $IPaddr = $matches[1];
                                                        echo "matching ipaddr next" |  Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50;;   
                                                        }
                                                    }
                                                }
                                            $i++;
                                            echo "incrementing i, $i $IPaddr $CompN" |  Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50;
                                            start-sleep -Seconds 1;
                                        } while ([String]::Isnullorempty($IPaddr) -or [String]::Isnullorempty($CompN) -and ($i -le 10))

                                    echo $ipaddr |  Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50;
                                    echo $CompN |  Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50;

                                    $sr.Dispose();
                                    $pipe.Dispose();

                                    } while ([String]::Isnullorempty($IPaddr) -or [String]::Isnullorempty($CompN))
                           
                            $HashVMInfo.add("IPADDR",$ipaddr)
                            $HashVMInfo.add("COMPN",$CompN)
                            echo "Both values set : $ipaddr" |  Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50;
                            echo "Both values set : $CompN" |  Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50;
                            return $HashVMInfo
                            } #Close function

                        #start-sleep -Seconds 10
 
                        #Set variables

                        echo $OSName $VMIndex | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50

                        $OSIDtoMasterVHDfilename = @{};

                        $OSIDtoMasterVHDfilename.Add("W2K8R2","MASTER-2K8R2-ENT-FR-VL-SP1-disk0.vhdx");
                        $OSIDtoMasterVHDfilename.Add("W2K3R2","MASTER-2K3R2-ENT-FR-VL-SP1-disk0.vhdx");
                        $OSIDtoMasterVHDfilename.Add("W2K12","MASTER-2K12-STD-FR-VL-disk0.vhdx");
                        $OSIDtoMasterVHDfilename.Add("W2K12R2","MASTER-2K12R2-DAT-FR-VL-disk0.vhdx");

                        [string]$vmSwitch = "VS1"; #Change this to your preferred NIC.
                        [string]$vmSwitch2 = "PROVISION"; #Change this to your preferred NIC.
                       
                        $OSVHD = $OSIDtoMasterVHDfilename.Get_Item($OSName);
                        [string]$parentVHD = "C:\Hyper-V\MASTERS\MASTER-2K8R2-ENT-FR-VL-SP1-AVT2-SYSPREP\Virtual Hard Disks\" + $OSVHD  #Change this to your preferred Parent VHD.
                        [string]$vmPath = "C:\ClusterStorage\Volume1";  #Change this to your preferred VM Store.           

                        [string]$vmMemory = concat($RAM, " GB")
                        <#
                        Trap [Exception]
                        {
                        Write-Host -BackgroundColor Red -ForegroundColor White "Error, The memory amount should be an integer!"
                        break
                        } #CloseTrap           

                        [int]$vmCPU = $Cores
                        Trap [Exception]
                        {
                        Write-Host -BackgroundColor Red -ForegroundColor White "Error, The memory amount should be an integer!"
                        break
                        } #CloseTrap           
                        #>

                        #Create Raw Virtual Machine
                        $logfile = "c:\logfile_" + $vmName + ".txt"
                        $NewVHD = $vmPath + "\" + $vmName + "-disk0.vhdx"
                        #New-VM -Name $vmName


                        #echo $OSName $VMIndex | Out-File -FilePath "c:\LogB$OSName$VMIndex.txt" -Append -width 50

                        New-VM -Name $vmName -Generation 2 -Path $vmPath -NoVHD | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50
                        Set-VMFirmware -VMName $vmName -EnableSecureBoot off
                        start-sleep -Seconds 5
                        Set-VMProcessor -VMName $vmName -Count 2 | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50
                        start-sleep -Seconds 5
                        Set-VMMemory -VMName $vmName -StartupBytes 2147483648 #| Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50
                        start-sleep -Seconds 5
                        Add-VMNetworkAdapter -VMName $vmName -SwitchName $vmSwitch | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50           
                        Add-VMNetworkAdapter -VMName $vmName -SwitchName $vmSwitch2 | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50        
                        Set-VMComPort -VMName $vmName -Number 1 -Path "\\.\pipe\$vmName" | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50
                        ##Create a Differencing VHD
                        ##New-VHD -VHDPaths $vmPath\$vmName.vhd -ParentVHDPath $parentVHD | Out-File -FilePath .\Log.txt -Append -width 50           
                        Copy-Item $parentVHD $NewVHD
                        ##Check if the VHD has been created and attach it on success
 
 
                        $VHDExists = Test-Path $NewVHD

                        if ($VHDExists -eq $True)
                            {
   
                            Add-VMHardDiskDrive -VMName $vmName -ControllerType "SCSI" -Path $NewVHD | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50
                            #Write-Host -BackgroundColor Green -ForegroundColor Black "Virtual Machine $vmName has been successfully created"            
                            echo "VM+DISK OK" | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50
                           
                            $snaploc = $vmPath + "\" + $vmName + "\Snapshots"
                            $smartpage = $vmPath + "\" + $vmName + "\SmartPaging"
                       
                            Set-VM -name $vmName -SnapshotFileLocation $snaploc -SmartPagingFilePath $smartpage
                            $bootorder = (Get-VMFirmware $vmName).BootOrder
                            $bootorder | fl Device |  Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50
                            $disk1 = $bootorder[2]
                            $net1 = $bootorder[0]
                            $net2 = $bootorder[1]
                            Set-VMFirmware -VMName $vmName -BootOrder $disk1,$net1,$net2
                           
                            } #Close If
                        else
                            {
                            #Write-Host -BackgroundColor Red -ForegroundColor White "It seems the disk creation job has not completed, attach it manually once it's done"
                            echo "VM+DISK PB" | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50
   
                            } #Close Else
 
                        $InstallAD = {       }
                        $InstallExchange = { }
                        #$displaycomputername =
                        #                        { gc env:path | out-file -FilePath C:\scripts\remote-log.txt -Append;
                        #                          gc env:computername | out-file -FilePath C:\scripts\remote-log.txt -Append;
                        #                        }   
             
                        #Start the machine
                        $startMachine = "Y"
                   
                        if ($startMachine.toUpper() -eq "Y")
                            {
                            $VMInfo = @{}
                            New-VMConnectSession -VMName $vmName
                            echo "will start vm $vmName" | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50
                            Start-VM -VMName $vmName
                            echo "vm $vmName will wait for start to complete" | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50
                            Start-Sleep -Seconds 30
                            echo "vm $vmName started, will wait for serial data" | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50
                            $VMinfo = GetVMInfo($vmName)
                            echo "vm $vmName got serial data $VMinfo" | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50
                            Start-Sleep -Seconds 180
                            echo "vm $vmName will wait 180 sec for winrm client script to finish" | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50
                            $IPADDR = $VMinfo.Item("IPADDR")
                            echo $IPADDR | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50
                           
                            $username = "administrator"
                            $passwordPath = "Q:\securestring.txt"
                            echo "vm $vmName loaded username and secure password string" | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50

                            $pwd = ConvertTo-SecureString (Get-Content -Literalpath $passwordPath)
                            $cred = New-Object System.Management.Automation.PSCredential -ArgumentList @($username,$pwd )
                            echo "vm $vmName credentials loaded" | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50

                            $sessionopt = New-PSSessionOption -SkipCACheck -SkipCNCheck
                            echo "vm $vmName remote ps session options loaded, will start remote session on $IPADDR" | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50

                            #Invoke-command -ComputerName $VMinfo.Get_Item("IPADDR") -ScriptBlock $displaycomputername -UseSSL -Credential $cred -SessionOption $sessionopt | Out-File -FilePath "c:\Log-commande.txt" -Append -width 50
                            Invoke-command -ComputerName $VMinfo.Item("IPADDR") -ScriptBlock { gc env:computername | out-file -FilePath C:\scripts\remote-log.txt -Append; } -UseSSL -Credential $cred -SessionOption $sessionopt | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50
                           
                      

                            # Start-Job -FilePath "$((Get-Location).path)\runremote.ps1" -ArgumentList @($IPADDR)
                            # & runremote.ps1 $VMinfo.Item("IPADDR")

                            echo "vm $vmName remote ps session ended" | Out-File -FilePath "c:\Log$OSName$VMIndex.txt" -Append -width 50
                            } #Close If
   

                         return 0
                         } #close block RUNFACVM
 
             $erroractionpreference = 0
 
             for($i=1;$i -le $numOfVMs2;$i++)
                       
                {
 
                Start-Job -Name "runFACVM" -ScriptBlock $runFACVM -ArgumentList $OSName2, $i, $RAM2, $Cores2
   
                } #Close for

                #Start-Job -Name "runFACVM" -ScriptBlock $runFACVM -ArgumentList $OSName2, $i, $RAM2, $Cores2
 
             Get-Job -Name "runFACVM" | Wait-Job -Timeout 1800
             Get-Job -Name "runFACVM" | Receive-Job
         

            return 0

            }


$HashOS = @{}
$HashOSParam = @{}

$ARRserverformula =  $serverformula.split(";")

foreach($server in $ARRserverformula)

        {

        $ARR2serverformula = $server.split(":")
        $HashOS.add($ARR2serverformula[0],$ARR2serverformula[1])
        $HashOSParam.add($ARR2serverformula[0],@($ARR2serverformula[2],$ARR2serverformula[3]))

        write-host $ARR2serverformula[0]
        write-host $ARR2serverformula[1]
        write-host $ARR2serverformula[2]
        write-host $ARR2serverformula[3]


        Start-Job -Name "runINSTVM" -ScriptBlock $runINSTVM -ArgumentList $ARR2serverformula[0], $ARR2serverformula[1], $ARR2serverformula[2], $ARR2serverformula[3]

        }

        Get-Job -Name "runINSTVM" | Wait-Job -Timeout 3600

        #Instantiate-VM $ARR2serverformula[0] $ARR2serverformula[1] $ARR2serverformula[2] $ARR2serverformula[3]

        Get-Job -Name "runINSTVM" | Receive-Job




#########################################################
# END SERVER SCRIPT
#########################################################
#########################################################
# CLIENT SCRIPT 1 (PROVIDES INFO to the hypervisor) save it as canalwr2.ps1 in C:\scripts
#########################################################

[System.IO.Ports.SerialPort]::getportnames()

$ipres = Get-WMIObject -Class 'Win32_NetworkAdapterConfiguration' -Filter "IPEnabled = True AND DNSDOmain = 'provision.local'"
$computername = gc env:computername

$port = new-Object System.IO.Ports.SerialPort COM1,9600,None,8,one
$port.open()
$port.WriteLine("#IPADDR=" + $ipres.IPAddress[0] + "#")
Start-Sleep -Seconds 10
$port.WriteLine("#COMPN=" + $computername +  "#")

$port.close()

#########################################################
# END CLIENT SCRIPT 1
#########################################################

#########################################################
# CLIENT SCRIPT 2 (configures WinRM and powershell on the VM to allow remote provisioning)
# requires a special unattend.xml to make it run after sysprep and requires makecert.
#Save as winrmconf.ps1 in C:\scripts
#########################################################

#$compname = gc env:computername;
$computerdata = Get-WmiObject -Class 'Win32_ComputerSystem'
$compname = $computerdata.Name

$hvname =  "PROVISION-HYPERV";
[string]$stopparser = '--%';

& "c:\scripts\makecert.exe" $stopparser "-sk "makecert" -ss my -sr localmachine -r -n "CN=$compname" -eku 1.3.6.1.5.5.7.3.1"  | Out-File "c:\scripts\logwinrm.log" -Append

$cert = Get-ChildItem cert:\Localmachine\my | Where-Object {$_.Subject -eq "CN=$compname"}


[string]$trustedhosts = [system.string]::Concat("@{TrustedHosts=`"",$hvname,"`"}");
write-host $trustedhosts;

& "winrm" $stopparser "s winrm/config/client $trustedhosts"  | Out-File "c:\scripts\logwinrm.log" -Append

[string]$winrmparams = [system.string]::Concat("@{Hostname=`"",$compname,"`";CertificateThumbprint=`"",$cert.thumbprint,"`"}");
write-host $winrmparams;
start-sleep -Seconds 10;
& "winrm" $stopparser "delete winrm/config/Listener?Address=*+Transport=HTTPS" | Out-File "c:\scripts\logwinrm.log" -Append
start-sleep -Seconds 10;
& "winrm" $stopparser "create winrm/config/Listener?Address=*+Transport=HTTPS $winrmparams" | Out-File "c:\scripts\logwinrm.log" -Append

#########################################################
# END CLIENT SCRIPT 2
#########################################################

#########################################################
# SCHEDULED TASK XML (launches c:\scripts\canalwr2.ps1 on computer startup after the syprep has done it's work - reports IP and name of the VM to the hypervisor thru the com port)
#########################################################

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.3" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2014-01-16T11:22:12.486875</Date>
    <Author>COMPUTERNAME\Administrator</Author>
  </RegistrationInfo>
  <Triggers>
    <BootTrigger>
      <Repetition>
        <Interval>PT5M</Interval>
        <Duration>P1D</Duration>
        <StopAtDurationEnd>false</StopAtDurationEnd>
      </Repetition>
      <ExecutionTimeLimit>PT30M</ExecutionTimeLimit>
      <Enabled>true</Enabled>
    </BootTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">
      <UserId>COMPUTERNAME\Administrator</UserId>
      <LogonType>Password</LogonType>
      <RunLevel>LeastPrivilege</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <AllowHardTerminate>true</AllowHardTerminate>
    <StartWhenAvailable>false</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
    <UseUnifiedSchedulingEngine>false</UseUnifiedSchedulingEngine>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>P3D</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
      <Arguments>C:\Scripts\canalwr2.ps1</Arguments>
    </Exec>
  </Actions>
</Task>

#########################################################
# END SCHEDULED TASK XML
#########################################################


#########################################################
# UNATTEND.XML (launches c:\scripts\winrmconf.ps1 on computer startup at the specialize pass and autologon)
#########################################################

<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <settings pass="generalize">
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <DoNotCleanTaskBar>true</DoNotCleanTaskBar>
            <RegisteredOrganization>YOUR ORGANIZATION</RegisteredOrganization>
            <RegisteredOwner>YOUR ORGANIZATION</RegisteredOwner>
        </component>
    </settings>
    <settings pass="specialize">
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
            <ComputerName></ComputerName>
            <ProductKey>Windows product key here</ProductKey>
            <RegisteredOrganization>YOUR ORGANIZATION</RegisteredOrganization>
            <RegisteredOwner>YOUR ORGANIZATION</RegisteredOwner>
            <TimeZone>Romance Standard Time</TimeZone>
            <CopyProfile>true</CopyProfile>
        </component>
        <component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <RunSynchronous>
                <RunSynchronousCommand wcm:action="add">
                    <Description>EnableAdmin</Description>
                    <Order>1</Order>
                    <Path>cmd /c net user Administrator /active:yes</Path>
                </RunSynchronousCommand>
                <RunSynchronousCommand wcm:action="add">
                    <Description>UnfilterAdministratorToken</Description>
                    <Order>2</Order>
                    <Path>cmd /c reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v FilterAdministratorToken /t REG_DWORD /d 0 /f</Path>
                </RunSynchronousCommand>
                <RunSynchronousCommand wcm:action="add">
                    <Description>disable user account page</Description>
                    <Order>3</Order>
                    <Path>cmd /c reg add HKLM\Software\Microsoft\Windows\CurrentVersion\Setup\OOBE /v UnattendCreatedUser /t REG_DWORD /d 1 /f</Path>
                </RunSynchronousCommand>
            </RunSynchronous>
        </component>
        <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <InputLocale>040C:0000040C</InputLocale>
            <SystemLocale>fr-FR</SystemLocale>
            <UILanguage>fr-FR</UILanguage>
            <UserLocale>fr-FR</UserLocale>
        </component>
    </settings>
    <settings pass="oobeSystem">
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
            <UserAccounts>
                <AdministratorPassword>
                    <Value>crypted admin password</Value>
                    <PlainText>false</PlainText>
                </AdministratorPassword>
            </UserAccounts>
            <AutoLogon>
                <Enabled>true</Enabled>
                <Username>Administrator</Username>
                <Domain>.</Domain>
                <Password>
                    <Value>crypted admin password</Value>
                    <PlainText>false</PlainText>
                </Password>
                <LogonCount>5</LogonCount>
            </AutoLogon>
            <Display>
                <ColorDepth>32</ColorDepth>
                <HorizontalResolution>1024</HorizontalResolution>
                <RefreshRate>60</RefreshRate>
                <VerticalResolution>768</VerticalResolution>
            </Display>
            <OOBE>
                <HideEULAPage>true</HideEULAPage>
                <NetworkLocation>Work</NetworkLocation>
                <ProtectYourPC>1</ProtectYourPC>
            </OOBE>
            <RegisteredOrganization>YOUR ORGANIZATION</RegisteredOrganization>
            <RegisteredOwner>YOUR ORGANIZATION</RegisteredOwner>
            <TimeZone>Romance Standard Time</TimeZone>
            <FirstLogonCommands>
                <SynchronousCommand wcm:action="add">
                    <Description>configure winrm remoting ssl listerner and certificate</Description>
                    <CommandLine>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe &quot;c:\scripts\winrmconf.ps1&quot;</CommandLine>
                    <Order>1</Order>
                    <RequiresUserInput>false</RequiresUserInput>
                </SynchronousCommand>
            </FirstLogonCommands>
        </component>
        <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <InputLocale>040C:0000040C</InputLocale>
            <SystemLocale>fr-FR</SystemLocale>
            <UILanguage>fr-FR</UILanguage>
            <UserLocale>fr-FR</UserLocale>
        </component>
    </settings>
    <settings pass="offlineServicing">
        <component name="Microsoft-Windows-PnpCustomizationsNonWinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <DriverPaths>
                <PathAndCredentials wcm:keyValue="1" wcm:action="add">
                    <Path>\Drivers</Path>
                </PathAndCredentials>
            </DriverPaths>
        </component>
    </settings>
    <cpi:offlineImage cpi:source="wim://srv-backup2/magasin/images-cd-os/windows%202008%20r2/fr_windows_server_2008_r2_with_sp1_vl_build_x64_dvd_617392/sources/install.wim#Windows Server 2008 R2 SERVERENTERPRISE" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
</unattend>

#########################################################
# END UNATTEND.XML
#########################################################


#########################################################
# SYSPREP COMMAND
#########################################################



c:\windows\system32\sysprep\sysprep.exe /unattend:c:\scripts\unattend.xml

#########################################################
# END SYPREP COMMAND
#########################################################



Monday, August 5, 2013

Windows 2012 Hyper-V Replica using a dedicated network is possible.

Hello.

We'll talk about Windows 2012 Hyper-V replica feature today.
This feature allows to replicate a living VM on a secondary Hyper-V Windows 2012 server.
The replication interval is 5 minutes, and is block incremental. The network overhead is quite low.

Two weeks ago, i had a customer request about dedicating a network for this replication traffic.

I thought that it could be interesting to do this since it offers :

-More security (total isolation)
-Increased performance in case of network saturation or if the network backbone is overwhelmed or slow (let's say 100 mbps)
-Increased resilience (replication would continue if the LAN backbone is out)

In this specific case, the replication network was not switched. It was simply made by connecting the source and replica destination servers by a patch network cable (direct attach, no switches involved)

I wondered too if i could add even more resilience by teaming the direct connection between the servers.
The reply is : yes.

So, i used two NICs on both sides too create a Windws 2012 teamed interface (on both servers).
Whenever, one NIC or cable fails or disconnect, the traffic continues to flow. I left all settings for the teaming at automatic. I was pleased to see that I got more than NFT mode (network fault tolerance, meaning only one link is used at a time) : The network reported 2 Gbps on both sides so it means there was load balancing at work.

Then I tested disconnecting and reconnecting the links one at a time, and validated that the NIC team was performing well on both sides.

Then, How to use this network for Hyper-V replication ?

The solution is extremely simple : edit hosts file  and override DNS settings on the source of replication server, assigning to the FQDN name of the replica server the IP declared on the replication dedicated network.

Make also sure that the rules of Windows Firewall allow replication using this network, you can do this easily by first declaring the dedicated team for replication to be a private network in Windows firewall. (you have to edit the local GPO of the server for this)

Then create the replica link.

Configure replication using Kerberos or SSL Certificates (both modes worked for me in this setup) on the replica destination server.

Then initiate replication of a VM from the source server using whatever mode. The replication should work.

Then you have to check that the replication network you created is indeed used by Hyper-V.

Use this command to check it on the source or destination server :

netstat -no| find ":80"
or
netstat -no | find ":443"

You should find sockets open on the IP addresses of the replication network and see traffic flowing every 5 minutes. You may check more in detail with the performance monitor, or network monitor, or wireshark.

Hope you will test this setup too and tell me what you think about it.

 Finally some important info :

Using a test environment at first is really important. Having a solid backup plan too is important. Do not rely on replications as a backup method.

DISCLAIMER :

Use the procedure above at your own risk. I am not to be held responsible of data loss or any damage done to your IT environment by applying the procedure above.