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.





Sunday, December 18, 2011

Converting a Clustered Shared Volume (CSV) from MBR to GPT without data loss

Today i converted a MBR disk to GPT without data loss. The goal is to overcome the 2TB volume limit
The disk was a CSV disk in a Windows 2008 R2 Hyper-V Cluster.

Here is the procedure :

  • Export the VM on your CSV or check your VM backups first in case something goes wrong
  • Put HA VMs offline in order to quiesce I/O on the CSV for additional security.
  • Put your CSV on maintenance mode. Check the node on which the CSV resource is online
  • Download GPT fdisk at http://sourceforge.net/projects/gptfdisk/ and extract on the node on wich the CSV is online
  • On the said node, run GDISK on a command prompt
  • Say yes to the prompt to accept MBR to GPT conversion
  • Type w to commit changes to the disk. It will not overwrite your partitions in the sense that it will erase data on it, so say yes again
  • Rescan disks in Disk management. Check the disk properties, volume, and verify that the disk type is now GPT
  • Disable maintenance mode on the cluster and disable redirected access, if unable to disable redirected access,take the CSV offline and back online
  • Put your HA VM back online. to test everything is OK before extending the Windows volume
  • Try to move your VM (ideally using live migration) from one node to the other and back. If unable to do so, stop the cluster service on the offending node and start it again using the cluster console. Retry moving VMs. It should work now.
  • Now you will be able to extend your Windows CSV volume beyond 2TB (you can stop VMs and put back on maintenance mode and extend on the node that owns the CSV disk if you want total security)
Notes : MBR to GPT should work fine in most scenarios (in my case 1 primary partition, large offset  (2048 sectors) to the beginning of the first partition to fit MBR structures, plenty of space at the end of the partition (since i was preparing an extension)

Thursday, September 22, 2011

D2D Backup revisited, this time using HP Smart Array and Windows 2008 R2

Here is an example of D2D backup with the possibility of vaulting (externalization) of the destination drive.
For this to work you will need a number of RAID0 disks that you will rotate by plugging/unplugging them.


On HP smart arrays there is no option to remove gracefully a RAID0 unit, and moreover it will place the unit in a "dirty" configuration upon reconnect, even if there was no I/O at the time of disk removal. This is quite a shame, so if you need a more robust solution, go for a D2D disk solution based on LSI 3Ware for instance.

Basically, there are two scheduled tasks, one for removal, and the other for reconnect. The first script will :
  1. Unmount the drive from the system using diskpart
The second task will call a script in ordrer to :
  1. Tell the HP smart array to ignore the status of the disk and make it online anyway (you can check the status in ACU before and after the first subtask execution).
  2. Make it online and mount it in Windows (using diskpart)
So here are the scripts, first to disconnect the drive :
dim oshell
set oshell = createobject("wscript.shell")

oshell.run "diskpart /s diskpart-remove.script",0, true

The diskpart "diskpart-remove.script" file :

select volume 3
remove
select disk 1
offline disk

And for reconnect :

dim oshell
set oshell = createobject("wscript.shell")

oshell.run chr(34) & "C:\Program Files (x86)\Compaq\Hpacucli\Bin\hpacucli.exe" & chr(34) & " ctrl slot=0 ld 2 modify reenable forced",0, true
wscript.sleep 60000
oshell.run "diskpart /s diskpart-add.script",0, true
wscript.sleep 10000
oshell.run "diskpart /s diskpart-add2.script",0, true


The diskpart "diskpart-add.script" file :

rescan
select disk 1
online disk

and finally, the diskpart "diskpart-add2.script" file :

rescan
select volume 3
assign

Note :
On Windows 2008 R2 (did not test on 2008) the diskpart "assign" command will mount the volume on the same drive letter it had at the moment of the diskpart "remove" command.

Do not forget to change the disk/volume IDs according to your configuration, and make sure that there are no I/O (backups) at the moment of drive disconnect !

I hope you will find these scripts useful !

Monday, August 1, 2011

Scripts for monitoring session logon, connect, logout, lock/unlock events across domain

Ok, here is a nice script to use for security purposes, time tracking or whatever fancy name you give to this solution
It is possible since Windows 2008 to use triggers like session lock, unlock, remote connect (RDP) in scheduled tasks. Moreover, the tasks run in the context of the user that triggered the event.
So basically we have to deploy tasks on all Vista/7/2008 R2 computers, tasks that will run when these events are triggered and will, for instance, insert in a database the user various session events (in our case we report by sending an HTTP request with additional data to a linux web server)
Additional data that is available is : user samaccountname, computer name where the event fired, client computer name in a RDP connect/logon event, type of event, of course event time is calculated at the database level.
To make all this work, we must first deploy the tasks on all computers. That will be done with a startup batch script in a GPO
but first you have to create all the tasks that will run our master script (the one that reports events) on a reference computer :
do not forget :
One task = one trigger = one command line with the type of event as a script argument
and do not forget to allow an adequate user group to run the task. Windows will run the task in the context of a user if he is member of the group.
now define the scheduled task action :
Easy : path to the script + EVENT TYPE as argument
(I could not find a way to know during script runtime what trigger fired the task) so we have to create as much scripts than the events we wish to monitor.
command line exemple :
c:\windows\scripts\user-event-monitor.vbs RCONNECT
You may use any moniker, i use these :
LOGON (session logon)
LOGOFF (session logoff / task trigger DOES NOT EXIST, you have to use a GPO logoff script))
RCONNECT (remote, RDP connect)
RDISCONNECT (RDP disconnect)
LCONNECT (connect to a user session by console, when switching users on Vista/7)
LDISCONNECT (see above, disconnect)
LOCK (session lock)
UNLOCK (session unlock)
So it makes a bunch of tasks, now you have to export their definition using SCHTASKS and the XML switch and create a batch to import them on all client computers and RDS servers. (by GPO as said above)
Here is the deployment batch :

mkdir %windir%\scripts
xcopy file://domain.local/NETLOGON/User-Monitor.vbs %windir%\scripts\User-Monitor.vbs
xcopy file://domain.local/NETLOGON/User-Monitor-RConnect.xml %windir%\scripts\User-Monitor-RConnect.xml
xcopy file://domain.local/NETLOGON/User-Monitor-LConnect.xml %windir%\scripts\User-Monitor-LConnect.xml
xcopy file://domain.local/NETLOGON/User-Monitor-RDisconnect.xml %windir%\scripts\User-Monitor-RDisconnect.xml
xcopy file://domain.local/NETLOGON/User-Monitor-LDisconnect.xml %windir%\scripts\User-Monitor-LDisconnect.xml
xcopy file://domain.local/NETLOGON/User-Monitor-Logon.xml %windir%\scripts\User-Monitor-Logon.xml
xcopy file://domain.local/NETLOGON/User-Monitor-Lock.xml %windir%\scripts\User-Monitor-Lock.xml
xcopy file://domain.local/NETLOGON/User-Monitor-Unlock.xml %windir%\scripts\User-Monitor-Unlock.xml
schtasks /create /xml %windir%\scripts\User-Monitor-LConnect.xml /tn "User-Monitor-LConnect"
schtasks /create /xml %windir%\scripts\User-Monitor-RConnect.xml /tn "User-Monitor-RConnect"
schtasks /create /xml %windir%\scripts\User-Monitor-LDisonnect.xml /tn "User-Monitor-LDisconnect"
schtasks /create /xml %windir%\scripts\User-Monitor-RDisconnect.xml /tn "User-Monitor-RDisconnect"
schtasks /create /xml %windir%\scripts\User-Monitor-Logon.xml /tn "User-Monitor-Logon"
schtasks /create /xml %windir%\scripts\User-Monitor-Lock.xml /tn "User-Monitor-Lock"
schtasks /create /xml %windir%\scripts\User-Monitor-Unlock.xml /tn "User-Monitor-Unlock"

and here is the master reporting script :

on error resume next
set onet = createobject("wscript.network")
set oshell = createobject("wscript.shell")
Set objWinHttp = CreateObject("WinHttp.WinHttpRequest.5.1")
set oargs = wscript.arguments
set regex = new regexp
lngTimeout = 59000
regex.pattern = ">rdp-tcp#\d+\s+\S+\s+(\d+)"
set oexec = oshell.exec("query session " & onet.username)
oexec.stdout.readline
line = oexec.stdout.readline
set omatches = regex.execute(line)
set submatches = omatches(0).submatches
sessionid =  submatches(0)
clientname = oshell.regread("HKCU\Volatile Environment\" & sessionid & "\CLIENTNAME")
objWinHttp.SetTimeouts lngTimeout, lngTimeout, lngTimeout, lngTimeout
objWinHttp.open "GET" , "https://example.local/usermonitor.php?nav_user=" & onet.username & "&nav_file=" & oargs(0) & "&nav_module=" & onet.computername & "&nav_session=" & clientname
objwinhttp.send


I had to make some strange code in order to get the RDS "CLIENTNAME" variable. It is specific to Windows 2008 / R2  i never had trouble to get the var on Windows 2003
Also, the logoff event is not managed by a task since the trigger DOES NOT EXIST, the solution is to create a GPO with a logoff script.
Have a nice day !

Saturday, June 11, 2011

if you hate tape backups and need to vault (externalize), this script is for you !

If you are an happy owner of a decent RAID HBA like 3Ware, and you cannot stand tape backups, there are options : USB2 drives, ok, too slow. let's wait for USB3 compliant servers. The other option are e-SATA drives, faster, but somewhat big (the enclosure), and such a chore to take it with you.

Instead, you can choose a DTD backup (disk to disk) with the externalized set of disks manage by your internal HBA (of course you need a disk in a tray, hot pluggable) the majority of entry and middle servers have it so it should not be a problem.

So the strategy is : Backup at non production hours (if possible) to a RAID5 array.
Create a duplication job to a RAID0 1TB or 2TB disk (only one disk)
After the duplication is finished, issue a windows command to unmount the disk.
When it is done, issue a HBA CLI command to REMOVE the single RAID0 disk (not DELETE) The following script also manages rotation on five disks to ensure that the correct disk is inserted based on day.

Works well on Windows 2003 R1 R2, but on some servers i had an issue : the disk GUID would not change after replacing a disk, so test it well in you environment !
PS : tested with Symantec backup Exec

Here comes the code :



GUID_Lundi = "\\?\Volume{b33c7f05-0faa-497b-be6c-27e9be089cea}\"
GUID_Mardi = "\\?\Volume{5530f634-8fa9-11dd-99e0-40001abd57ea}\"
GUID_Mercredi = "\\?\Volume{5530f641-8fa9-11dd-99e0-40001abd57ea}\"
GUID_Jeudi = "\\?\Volume{e2a17ddf-955a-11dd-8103-4000a7d5a89c}\"
GUID_Vendredi = "\\?\Volume{e2a17de9-955a-11dd-8103-4000a7d5a89c}\"

Set ofso = createobject("scripting.filesystemobject")
set oshell = createobject("wscript.shell")
set ofile = ofso.opentextfile("c:\scripts\log.txt",8,true)



Dim Day

Day = DatePart("w", now)

ofile.writeline "script start, inventory of disks - day " & Day & " of week"
oshell.run "c:\scripts\tw_cli.exe maint rescan c0" , 0 , true
ofile.writeline "rescan controller done, waiting 30 secs"

wscript.sleep 30000

strComputer = "."

Set objWMIService = GetObject("winmgmts:" _

& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set colItems = objWMIService.ExecQuery("Select * from Win32_Volume")



For Each objItem In colItems



ofile.writeline "Found disk:"

ofile.Writeline "Device ID: " & objItem.DeviceID

ofile.Writeline "Drive Letter: " & objItem.DriveLetter

ofile.Writeblanklines 1

Select case objItem.DeviceID

Case Guid_Lundi


if day = 2 then


oshell.run "mountvol E: " & Guid_Lundi

ofile.Writeline "monday disk found - mount attempted"

wscript.sleep 30000

oshell.run "label E: BACKUP-LUNDI"

ofile.Writeline "labelling disk BACKUP-LUNDI"

Else


oshell.run "mountvol E: " & Guid_Lundi & " /d"

ofile.Writeline "wrong disk found - unmount attempted"
wscript.sleep 30000

oshell.run "c:\scripts\tw_cli.exe maint remove c0 u2",0, true

ofile.Writeline "wrong disk found - remove disk command executed"

End if



Case Guid_Mardi

if day = 3 then


oshell.run "mountvol E: " & Guid_Mardi

ofile.Writeline "tuesday disk found - mount attempted"


wscript.sleep 30000

oshell.run "label E: BACKUP-MARDI"

ofile.Writeline "labelling disk BACKUP-MARDI"

Else

oshell.run "mountvol E: " & Guid_Mardi & " /d"

ofile.Writeline "wrong disk found - unmount attempted"


wscript.sleep 30000

oshell.run "c:\scripts\tw_cli.exe maint remove c0 u2",0, true

ofile.Writeline "wrong disk found - remove disk command executed"


End if


Case Guid_Mercredi


if day = 4 then


oshell.run "mountvol E: " & Guid_Mercredi

ofile.Writeline "wednesday disk found - mount attempted"


wscript.sleep 30000

oshell.run "label E: BACKUP-MERCREDI"

ofile.Writeline "labelling disk BACKUP-MERCREDI"


Else


oshell.run "mountvol E: " & Guid_Mercredi & " /d"

ofile.Writeline "wrong disk found - unmount attempted"


wscript.sleep 30000

oshell.run "c:\scripts\tw_cli.exe maint remove c0 u2",0, true

ofile.Writeline "wrong disk found - remove disk command executed"


End if

Case Guid_Jeudi

if day = 5 then


oshell.run "mountvol E: " & Guid_Jeudi

ofile.Writeline "thursday disk found - mount attempted"


wscript.sleep 30000

oshell.run "label E: BACKUP-JEUDI"

ofile.Writeline "labelling disk BACKUP-JEUDI"


Else


oshell.run "mountvol E: " & Guid_Jeudi & " /d"

ofile.Writeline "wrong disk found - unmount attempted"


wscript.sleep 30000

oshell.run "c:\scripts\tw_cli.exe maint remove c0 u2",0, true

ofile.Writeline "wrong disk found - remove disk command executed"


End if


Case Guid_Vendredi



if day = 6 then

oshell.run "mountvol E: " & Guid_Vendredi

ofile.Writeline "friday disk found - mount attempted"

wscript.sleep 30000

oshell.run "label E: BACKUP-VENDREDI"

ofile.Writeline "labelling disk BACKUP-VENDREDI"

Else


oshell.run "mountvol E: " & Guid_Vendredi & " /d"

ofile.Writeline "wrong disk found - unmount attempted"

wscript.sleep 30000

oshell.run "c:\scripts\tw_cli.exe maint remove c0 u2",0, true

ofile.Writeline "wrong disk found - remove disk command executed"


End if


Case Else


ofile.writeline "not a listed removable backup drive, ignoring"

End Select


Next
ofile.close

Friday, June 10, 2011

Ghetto style mailbox sync between two exchange 2003 servers

Hello folks, some 5 years ago I figured out that it could be easy to synchronize the contents of mailboxes from one server to the other. i came up with a rather clumsy way of doing it : using ExIFS (R.I.P. exifs ! this feature is now absent from Exchange 2K7 and Exchange 2K10)

So if you're broke and cant afford Exchange 2007 / 2010 and their fancy SCR, CCR, DAG features, this is for you.

So what's the big idea : All you need is a little registry tweak in order to make exifs available thru the M: drive (like on Exchange 2000)
The robocopy tool and a little VBS script in a scheduled task.

Alas, it seems that it works only well with tiny mailboxes (less than 200 MB, 5000 mails) , but i didn't try all the robocopy switches, so maybe there is a way to avoid partial replications (missing mails).

Some drawbacks : folders must have the same names (useful to edit them on the passive server with outlook /resetfoldernames or just rename thru Exifs) And contacts are not happy with this method of syncing

And finally do not forget to put the send as / read as for your users to access their backup mbx, and also for the identity that run the script not to be blocked by "access denied"

So here is the script :



on error resume next


Const ADS_PROPERTY_CLEAR = 1
Const ADS_PROPERTY_UPDATE = 2
Const ADS_PROPERTY_APPEND = 3
Const ADS_PROPERTY_DELETE = 4

Dim AliasExchange
Dim IsMailSync

Set oshell = createobject("wscript.shell")
Set oFSO = createobject("scripting.filesystemobject")

Set oFile = oFSO.opentextfile("c:\scripts\mailsyncSRV1toSRV2.log",8,true)

ofile.writeline "##############début de synchronisation SRV1 vers SRV2 :" & Date() & " " & Time() & "#####################"
ofile.writeblanklines 2


set objParent = GetObject("LDAP://OU=allusers,DC=domain,DC=local")
objParent.Filter = Array("user")


for each objUser in objParent

AliasExchange = objuser.mailnickname


objuser.getinfo
err.clear
IsMailSync = objuser.get("extensionAttribute4")

if err.number <> 0 then isMailSync = ""


err.clear

if IsMailSync = "NoMailSync" then

ofile.writeline "Synchro de boite aux lettres desactivée pour : " & aliasexchange

Else

CheckFolderNames(aliasexchange)

ofile.writeline "Synchronisation de : " & aliasexchange & " debutée à : " & date() & " " & time()

oshell.run """C:\Program Files\Windows Resource Kits\Tools\robocopy.exe"" ""\\srv1\MBX$\" & AliasExchange & """ ""M:\domain.com\MBX\sec." & AliasExchange & """ /XD ""\\srv1\MBX$\" & AliasExchange & "\Contacts" & """ /E /MIR /XC /XA:H /R:0 /W:0 /NP /NFL /LOG+:c:\Scripts\sync-mbx-srv1-to-srv2.log", 0

ofile.writeline "Synchronisation de : " & aliasexchange & " finie à : " & date() & " " & time()

if err.number <> 0 then ofile.writeline err.number & " " & err.description

end if

Next

ofile.writeline "##############Fin de synchronisation SRV1->SRV2 :" & Date() & " " & Time() & "#####################"
ofile.writeblanklines 2

ofile.close


Sub CheckFolderNames(Alias)

on error resume next

set ofso = createobject("scripting.filesystemobject")
dim ofolder
dim odestfolder
dim erra
dim errb

err.clear
set ofolder = ofso.getfolder("\\srv1\MBX$\" & alias & "\Éléments envoyés")
set ofolder = ofso.getfolder("\\srv1\MBX$\" & alias & "\Éléments supprimés")
erra = err.number

err.clear
set ofolder = ofso.getfolder("\\srv1\MBX$\" & alias & "\Eléments supprimés")
set ofolder = ofso.getfolder("\\srv1\MBX$\" & alias & "\Eléments envoyés")
errb = err.number
err.clear

if (erra = 0) and (errb = 0) then exit sub

if erra = 0 then

set odestfolder = ofso.getfolder("M:\DOMAIN.COM\MBX\sec." & alias & "\Eléments envoyés")
odestfolder.name = "Éléments envoyés"
set odestfolder = ofso.getfolder("M:\DOMAIN.COM\MBX\sec." & alias & "\Eléments supprimés")
odestfolder.name = "Éléments supprimés"
err.clear

elseif errb = 0 then


set odestfolder = ofso.getfolder("M:\DOMAIN.COM\MBX\sec." & alias & "\Éléments envoyés")
odestfolder.name = "Eléments envoyés"
set odestfolder = ofso.getfolder("M:\DOMAIN.COM\MBX\sec." & alias & "\Éléments supprimés")
odestfolder.name = "Eléments supprimés"
err.clear

end if

End Sub