Tuesday, December 17, 2013

Slow PowerShell CSV reading and object generation

I am attempting to read a CSV file and decided the best way to do it was with PowerShell's native import-csv tool.  What was required was to read this CSV and then generate a registry file for import into numerous computers.  This was my result:

########################################################################################
#
#  Created by Trentent Tye
#             IBM Intel Server Team
#             December 13, 2013
#
# Description: This script will take a CSV file that contains values that are flexible
# and maintained by the MetaVision team and create a .reg file and import that file
# into the computer it's run on.
#########################################################################################

Get-Date
$csv = Import-csv .\MetaVision.csv

$output = "Windows Registry Editor Version 5.00`n`n"

$date = get-date
write-host first run $date
Foreach ($server in $csv.Server) { 
$regObject = $csv | Where {$_.server -eq $Server}
$output += "[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\iMD Soft\Citrix Clients\$server\Database Connect]`n"
$output += """Default Offline Mode""=""0""`n"
$output += """Default  Offline Mode""=""0""`n"
$output += """Domain Department""=""{0}""`n" -f $regObject.'Domain Department'
$output += """Default Department""=""{0}""`n" -f $regObject.'Default Department'
$output += """EMPI Database""=""{0}""`n" -f $regObject.'EMPI Database'
$output += """EMPI Server""=""{0}""`n" -f $regObject.'EMPI Server'
$output += """Offline Mode""=""0""`n"
$output += """Production Database""=""{0}""`n" -f $regObject.'Production Database'
$output += """Production Server""=""{0}""`n`n" -f $regObject.'Production Server'
$output += "[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\iMD Soft\Citrix Clients\$server\Settings]`n"
$output += """Application Size""=""1""`n"
$output += """Customize Units""=""0""`n"
$output += """DebugMode""=""0""`n"
$output += """DisplayLocalizationID""=""0""`n"
$output += """LastLogin""=""null""`n"
$output += """LocaleID""=""1033""`n"
$output += """LogFileMode""=""1""`n"
$output += """LogFilePath""=""""`n"
$output += """LoginPolicyMode""=""{0}""`n" -f $regObject.'LoginPolicyMode'
$output += """ProximityLockRange""=""0""`n"
$output += """ProximityObject""=""""`n"
$output += """ProximityUnlockRange""=""0""`n"
$output += """Silent""=""1""`n"
$output += """UseMRDDriver""=""0""`n"
$output += """UseVirtualKeyboard""=""0""`n"
$output += """Workstation BedID""=""{0}""`n" -f $regObject.'Workstation BedID'
$output += """Workstation LayoutID""=""""`n"
$output += """Workstation Type""=""{0}""`n" -f $regObject.'Workstation Type'
$output += """WriteActionsToLog""=""0""`n`n"
}

$date2 = get-date
$totaltime = ($date2 - $date).TotalSeconds / 60
write-host first complete $totaltime


rm "$env:temp\MetaReg.reg"
write-output $output | out-file -FilePath "$env:temp\MetaReg.reg"
Get-Date
write-host "Importing registry"
regedit /s "$env:temp\MetaReg.reg"
Get-Date

The CSV file we have has about 1500 lines in it.  To generate the registry key utilizing this method took 6 minutes and 45 seconds.  This is unacceptably slow.  I then started googling ways to speed up this processing and came across this article:
http://stackoverflow.com/questions/6386793/how-to-use-powershell-to-reorder-csv-columns

Where Roman Kuzmin suggested to handle the file as a text file instead of a PowerShell object.  The syntax used to generate convert the file into objects that can replace text as needed is a bit different but I decided to explore it.  His example code is as follows:


$reader = [System.IO.File]::OpenText('data1.csv')
$writer = New-Object System.IO.StreamWriter 'data2.csv'
for(;;) {
    $line = $reader.ReadLine()
    if ($null -eq $line) {
        break
    }
    $data = $line.Split(";")
    $writer.WriteLine('{0};{1};{2}', $data[0], $data[2], $data[1])
}
$reader.Close()
$writer.Close()
 
Essentially, he is proposing reading the file line by line and extracting the data by manually splitting the text string.  The string then turns into an array that you can use for substitution.  This is my final code using his example:

########################################################################################
#
#  Created by Trentent Tye
#             IBM Intel Server Team
#             December 13, 2013
#
# Description: This script will take a CSV file that contains values that are flexible
# and maintained by the MetaVision team and create a .reg file and import that file
# into the computer it's run on.
#########################################################################################


$date = get-date
write-host first run $date
if (test-Path $env:temp\MetaReg.reg) {rm "$env:temp\MetaReg.reg"}


$reader = [System.IO.File]::OpenText('MetaVision.csv')
$reader.ReadLine() # skip first line
$writer = New-Object System.IO.StreamWriter "$env:temp\MetaReg.reg"
$writer.WriteLine("Windows Registry Editor Version 5.00")
$writer.WriteLine("")

for(;;) {
    $line = $reader.ReadLine()
    if ($null -eq $line) {
        break
    }
    $data = $line.Split(",")
    $writer.WriteLine("[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\iMD Soft\Citrix Clients\{0}\Database Connect]`n", $data[0])
    $writer.WriteLine("""Default Offline Mode""=""0""`n")
    $writer.WriteLine("""Default  Offline Mode""=""0""`n")
    $writer.WriteLine("""Domain Department""=""{0}""`n", $data[2])
    $writer.WriteLine("""Default Department""=""{0}""`n", $data[1])
    $writer.WriteLine("""EMPI Database""=""{0}""`n", $data[3])
    $writer.WriteLine("""EMPI Server""=""{0}""`n", $data[4])
    $writer.WriteLine("""Offline Mode""=""0""`n")
    $writer.WriteLine("""Production Database""=""{0}""`n", $data[5])
    $writer.WriteLine("""Production Server""=""{0}""`n`n", $data[6])
    $writer.WriteLine("")
    $writer.WriteLine("[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\iMD Soft\Citrix Clients\{0}\Settings]`n", $data[0])
    $writer.WriteLine("""Application Size""=""1""`n")
    $writer.WriteLine("""Customize Units""=""0""`n")
    $writer.WriteLine("""DebugMode""=""0""`n")
    $writer.WriteLine("""DisplayLocalizationID""=""0""`n")
    $writer.WriteLine("""LastLogin""=""null""`n")
    $writer.WriteLine("""LocaleID""=""1033""`n")
    $writer.WriteLine("""LogFileMode""=""1""`n")
    $writer.WriteLine("""LogFilePath""=""""`n")
    $writer.WriteLine("""LoginPolicyMode""=""{0}""`n", $data[8])
    $writer.WriteLine("""ProximityLockRange""=""0""`n")
    $writer.WriteLine("""ProximityObject""=""""`n")
    $writer.WriteLine("""ProximityUnlockRange""=""0""`n")
    $writer.WriteLine("""Silent""=""1""`n")
    $writer.WriteLine("""UseMRDDriver""=""0""`n")
    $writer.WriteLine("""UseVirtualKeyboard""=""0""`n")
    $writer.WriteLine("""Workstation BedID""=""{0}""`n", $data[9])
    $writer.WriteLine("""Workstation LayoutID""=""""`n")
    $writer.WriteLine("""Workstation Type""=""{0}""`n", $data[10])
    $writer.WriteLine("""WriteActionsToLog""=""0""`n`n")
    $writer.WriteLine("")
}
$reader.Close()
$writer.Close()



$date2 = get-date
$totaltime = ($date2 - $date).TotalSeconds / 60
write-host first complete $totaltime



write-host "Importing registry"
regedit /s "$env:temp\MetaReg.reg"
Get-Date

The total time for this?  0.07 seconds.  Incredibly fast.  So, utilize the Import-CSV command and object based creation with caution.  Utilizing even a moderately sized file will take an unacceptable amount of time.

Wednesday, November 20, 2013

Microsoft and Citrix putting pictures of scripts in documents...

Come on guys, a little more effort than that.  How are you supposed to copy paste an image?

The script is the following:

It's supposed to pull the application and command-line to execute it in AppV5.  I got the script from this document:
http://www.microsoft.com/en-us/download/details.aspx?id=40885


"PackageName,Application Name,ApplicationPath"|out-file .\appPath.txt -Append
foreach($pkg in (gwmi -Namespace root\AppV -Class AppVClientPackage -Filter "IsPublishedGlobally = true"))
{

    $apps=gwmi -Namespace root\AppV -Class AppVClientApplication -Filter "PackageID = '$($pkg.PackageId)' and PackageVersionID = '$($pkg.VersionId)'"

   foreach($app in $apps)

   {

      $rootfolder=(Get-ItemProperty "HKLM:\Software\Microsoft\AppV\Client\Packages\$($app.PackageID)\Versions\$($app.PackageVersionID)\Catalog").Folder

      $appPath=$app.TargetPath.Replace("[{AppVPackageRoot}]",$rootFolder)

      "$($pkg.Name),$($app.Name),$($appPath)"|out-file .\AppPath.txt -Append
   }
}

Friday, November 15, 2013

Error launching batch file from within AppV 5 bubble

So we have an application that requires the "%CLIENTNAME%" variable to be passed to it in it's exe string.  The string looks like so:
prowin32.exe -p \\nas\cfgstart.p -param S,%CLIENTNAME%,120n,citrix,a92,10920 -wy

The issue we have is APPV does not seem to get that variable and pass it to the program.  So when the program starts, it makes %clientname% folders in a temp directory and we can't have two %clientname% folders in the same directory so only one instance of the application can be launched *period* if we do it this way, as opposed to one per server.

To resolve this issue I wrote a script that will pass the %CLIENTNAME% variable to AppV by ripping it out of the registry:

=======================================================
ECHO Launching Centricity...

for /f "tokens=1-3" %%A IN ('reg query "HKCU\volatile environment" /s ^| findstr /i /c:"CLIENTNAME"') DO SET CLIENTNAME=%%C

prowin32.exe -p \\nas\cfgstart.p -param S,%CLIENTNAME%,120n,citrix,a92,10920 -wy
=======================================================

This worked for AppV 4.6 without issue.  Now with AppV 5 I get an error, PATH NOT FOUND when trying to launch this script.


To verify the path exists in the app I ran the following commands:


The powershell commands put me in the AppV 5 bubble then opened a command prompt.  From the command prompt I can see the directory that is missing.  Going back to procmon I was curious to see what command it was launching.  It was launching this:

cmd /c ""C:\ProgramData\Microsoft\AppV\Client\Integration\3230251A-5B8E-47EF-8378-986B2A492D05\Root\VFS\Common Desktop\MyApps\BDM Pharmacy v9.2\Centricity Pharmacy Test on rxpv91cal.cmd" /appvve:3230251A-5B8E-47EF-8378-986B2A492D05_03CA3F94-F318-4693-A7E3-038DB30E6C70"

This command was failing.  It appears that when you are launching the .cmd file directly AppV 5 starts the cmd.exe *outside* the AppV bubble and it doesn't connect to the appvve.  To correct this I tried this command line:

cmd /c "cmd.exe /c "C:\ProgramData\Microsoft\AppV\Client\Integration\3230251A-5B8E-47EF-8378-986B2A492D05\Root\VFS\Common Desktop\MyApps\BDM Pharmacy v9.2\Centricity Pharmacy Test on rxpv91cal.cmd" /appvve:3230251A-5B8E-47EF-8378-986B2A492D05_03CA3F94-F318-4693-A7E3-038DB30E6C70"

Success!  It launched successfully and saw the directory and everything was good there after.  So let that be a lesson to other AppV 5 package makers, if you need a pre-launch script you may need to modify your published icon to put another cmd.exe /c before the command file for it to start in the bubble.

A very good AppV blog has already discovered this issue and came back with a better fix than mine:
http://blogs.technet.com/b/virtualvibes/archive/2013/10/17/the-issues-of-sequencing-bat-shortcuts-in-app-v-5-0.aspx

Wednesday, October 23, 2013

Citrix Provisioning Services (PVS) update failure

I recently got Event ID 0 on Citrix vDisk Update Service; "Update Task (UpdateTask) is not loaded"



The fix is to restart the Soap service on your chosen automatic update PVS server.





Tuesday, October 15, 2013

PowerCLI Fix VMWare Time Sync issue


# ===========================================================================================================
#
# Created by: Trentent Tye
#
# Creation Date: Oct 15, 2013
#
# File Name: Fix-Time-Sync.ps1
#
# Description: This script will be used to resolve an issue with VMWare where the VMWare Tools cause a
#                   time sync with the host.  If the host has an incorrect time it will knock the time out of
#                   sync on the guest.  To resolve this issue some text entires need to be made to the VMX
#                   fix.  This is detailed here:
#                   http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1189
#
# ===========================================================================================================

Add-PSSnapin VMware.VimAutomation.Core
connect-viserver wsvcenter20

$ExtraOptions = @{
    "tools.syncTime"="0";
    "time.synchronize.continue"="0";
    "time.synchronize.restore"="0";
    "time.synchronize.resume.disk"="0";
    "time.synchronize.shrink"="0";
    "time.synchronize.tools.startup"="0";
    "time.synchronize.tools.enable" = "0";
    "time.synchronize.resume.host" = "0";
}   # build our configspec using the hashtable from above.  I prefer this
# method over the use of files b/c it has one less needless dependency.
$vmConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
# note we have to call the GetEnumerator before we can iterate through
Foreach ($Option in $ExtraOptions.GetEnumerator()) {
    $OptionValue = New-Object VMware.Vim.optionvalue
    $OptionValue.Key = $Option.Key
    $OptionValue.Value = $Option.Value
    $vmConfigSpec.extraconfig += $OptionValue
}
# Get all vm's starting with name:
$VMView = Get-View -ViewType VirtualMachine -Filter @{"Name" = "WSCTX"}

foreach($vm in $VMView){
    $vm.ReconfigVM_Task($vmConfigSpec)
}

# Get all vm's starting with name:
$VMView = Get-View -ViewType VirtualMachine -Filter @{"Name" = "WSAPV"}

foreach($vm in $VMView){
    $vm.ReconfigVM_Task($vmConfigSpec)
}

Wednesday, September 25, 2013

IMA Service Fails with the Following Events: 3989, 3634, 3614

We have run into an issue where we have Provisioning Service 6.1 and XenApp 6.5 working together. After we update the vDisk (say, for Windows Update) we run through a script that does things like the "XenApp Prep" to allow the XenApp 6.5 ready for imaging. It appears that there is a bug in the XenApp Prep that sometimes causes it to not fully get XenApp 6.5 ready for rejoining the farm. The initial symptoms I found were:

Event ID 4003
"The Citrix Independent Management Architecture service is exiting. The XenApp Server Configuration tool has not been run on this server."

I found this CTX article about it, but nothing of it was applicable.

I did procmon traces and I found the following registry keys were missing on the bad system:


A broken system missing the Status Registry key



A working system with the Status key. Note Joined is "0"

After adding the Status Registry key:
===================================
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Citrix\IMA\Status]
"EnhancedDesktopExperienceConfigured"=dword:00000001
"Provisioned"=dword:00000001
"Ready"=dword:00000001
"Joined"=dword:00000000
===================================


I tried restarting the service and the progress bar got further but then still quit. Procmon showed me this:



That is ACCESS DENIED when trying to see that registry key. It turns out that the IMAService does not have appropriate permissions to read this key. The Magic Permissions on a working box and what you need to set here looks like this:



Notice that none of the permissions are inherited and "NETWORK SERVICE" is added with full control to this key. Now when we try and start the Citrix Independent Management Architecture service we get the following errors:

eventid 3989:
Citrix XenApp failed to connect to the Data Store. ODBC error while connecting to the database: S1000 -> General error: Invalid file dsn ''
eventid 3636:
The server running Citrix XenApp failed to connect to the data store. An unknown failure occurred while connecting to the database. Error: IMA_RESULT_FAILURE Indirect: 0 Server: DSN file:
eventid 3615
The server running Citrix XenApp failed to connect to the Data Store. Error - IMA_RESULT_FAILURE An unknown failure occurred while connecting to the database.
eventid 3609
Failed to load plugin C:\Program Files (x86)\Citrix\System32\Citrix\IMA\SubSystems\ImaWorkerGroupSs.dll with error IMA_RESULT_FAILURE
eventid 3601
Failed to load initial plugins with error IMA_RESULT_FAILURE

To correct these errors the local host cache needs to be rebuilt. To fix that we need to run:
Dsmaint recreatelhc
Dsmaint recreaterade

After doing that, we can start the IMA Service and MFCOM. If instead of IMA Service starting you get the following error message:
Eventid 4007
The Citrix Independent Management Architecture (IMA) service is exiting. The DSN File could not be updated with the information retrieved from Group Policy. Error: 80000001h DSN file: mf20.dsn DSN Entry: DATABASE Policy Setting: CTXDS Confirm that the Network Service has write permissions to the DSN File.

Ensure the following registry is populated:
reg add HKEY_LOCAL_MACHINE\PE_SOFTWARE\Wow6432Node\Citrix\IMA

/v DataSourceName /t REG_SZ /d "C:\Program Files (x86)\Citrix\Independent Management Architecture\mf20.dsn"

Wednesday, September 18, 2013

HyperV 2012 R2 "Enhanced Session Mode"

I hate it when I read "wow Hyper-V 2012 R2 now comes with enhanced session mode, allowing you to redirect audio, usb, etc to the client VM!"

I was excited to read about that until I also read it only works on Windows 8.1 and Server 2012 R2.  So much for it working on Windows 7 or other guest VM's.

Disappointing.

Friday, August 23, 2013

Lync PresenceIndicators while in UI Suppressed Mode in Outlook

The title is not possible.  You cannot run Lync in UISuppressedMode and still get the presence indicators in Outlook.  The versions I tried this with were Lync 2010 and Outlook 2010.  The reason is when you enable UISuppressedMode Microsoft disables the Lync.Model.Control which Outlook uses to enable the PresenceIndicators.

Since we use Outlook and Lync in Citrix and part of the requirement of us publishing Outlook was to have these presence indicators available (along with the call and other functions) UISuppressedMode was not an option.  In addition, we required the application to try and act as a single app, so when Outlook was closed, Lync would NOT hold the session.  The users requested Lync be hidden because two applications launching at the same time is confusing; so I wrote a script to try and simulate this functionality as much as possible.  This was the result:


;
; AutoIt Version: 3.0
; Language:       English
; Platform:       Win9x/NT
; Author:         Trentent Tye (trententtye@hotmail.com)
;
; Script Function:
;   Opens Lync in a hidden window, starts Outlook then polls the user session
;  to see if the outlook.exe process is still running.  If it is not it will
;  log off the session.
;
;

ShellExecute ( "C:\Program Files (x86)\Microsoft Lync\communicator.exe", "", "" , "" , @SW_HIDE )
WinWaitActive("Microsoft Lync","",5)
WinSetState ( "Microsoft Lync", "", @SW_HIDE )
Run ( "C:\Program Files (x86)\Microsoft Office\Office14\OUTLOOK.EXE")
Sleep(5000)


#include

AutoItSetOption ( "TrayIconHide" , 1 )
$arrProcesses = _GetProcessIds(@ComputerName)
_ArrayDisplay($arrProcesses)

Func _GetProcessIds($strHost)
    Local $var = EnvGet("SESSIONNAME") ;get session name of current session (eg RDP-TCP#0)
 
   ;match current session name to sessionID
   ;We do this by matching the current session's environment variable "SESSIONNAME" to the
   ;numeral registry value under HKCU\Volatile Environment which actually equals the session ID
   ;We do this because on Citrix you could be running multiple apps from the same server
   ;and we only want to disconnect the session that terminated the monitored application.
   For $i = 1 To 10
    Local $vol = RegEnumKey("HKEY_CURRENT_USER\Volatile Environment", $i)
    If @error <> 0 Then ExitLoop
   Local $key = RegRead("HKEY_CURRENT_USER\Volatile Environment\"& $vol, "SESSIONNAME")
     If $key == $var Then
        ;MsgBox(4096, "Found SessionID and Name", $vol & " " & $key )
        Local $session = $vol
     EndIf
    Next
 
   ;Now that we have the session ID we can query the list of all processes on this terminal
   ;and match the session ID returned per process and compare it to our current session ID
   ;MsgBox(4096, "Session is:", $session)
    While -1 ;infiniteloop
   $outlookFound = "0"
    If Not Ping($strHost,200) Then SetError(1)
    $objWMI = ObjGet("winmgmts:{impersonationLevel=impersonate}!\\" & $strHost & "\root\CIMV2")
    If Not IsObj($objWMI) Then SetError(2)
    $colItems = $objWMI.ExecQuery ("SELECT * FROM Win32_Process")
    Dim $arrResults[1][3] = [["Processname","ProcessID","SessionID"]]
   ;we now have a array of all processes on the system
 

    ;every 25 seconds we check to see if our process is running
    Sleep(25000) ;twenty-five seconds

    For $objItem In $colItems
        ReDim $arrResults[UBound($arrResults)+1][3]
       If $objItem.SessionId == $session Then ;we only want those processes that match our current session ID
          ;MsgBox(4096, "Session ID:", $objItem.SessionId)
          $arrResults[UBound($arrResults)-1][0] = $objItem.Name
          ;$arrResults[UBound($arrResults)-1][1] = $objItem.ProcessId
          ;$arrResults[UBound($arrResults)-1][2] = $objItem.SessionId
          ;MsgBox(4096, "Process Name", $objItem.Name)
          If $objItem.Name = "outlOok.exe" Then ;Is our process running?  If true set variable to 1
             $outlookFound = "1"
             ;MsgBox(4096, "Outlook Found", "Keep chugging along")
          EndIf
       EndIf
    Next
    if $outlookFound = "0" Then ;if process was found then this statement will be skipped
     ;MsgBox(4096, "Outlook Not Found", "Logging off")
     Run("shutdown.exe -l -f") ;Force a logoff
     ;Exit
     EndIf
    WEnd
 EndFunc


Tuesday, August 20, 2013

Query a bunch of Windows 2003 event logs

for /f %A IN ('type systems.txt') DO (
cscript.exe C:\windows\system32\EVENTQUERY.vbs /S %A /FI "ID eq 3001" /L Application >> list.txt
)

This will find event ID 3001 in the Application log file with a list of computers from "systems.txt"

Publish Outlook in Citrix with Lync Online Meeting but have Lync hidden

We have users that want to use Outlook but not Lync but do want the ability to make calls directly from Outlook to the user.  We have tried using UISuppressionMode but when that is launched through Citrix "communicator.exe" starts for a second then closes.  In my brief research it appears this is because communicator.exe is looking to be configured and utilized in a programatic way.  I was able to create a PowerShell script using the Lync 2010 SDK to launch Lync in this UI minimized way but the Outlook Lync plugin's would not communicate to the UISuppressed Lync.  This is the powershell script I bulit to do that:

#Importing SDK Dll
[string]$LyncModelDll = "C:\Program Files (x86)\Microsoft Lync\SDK\Assemblies\Desktop\Microsoft.Lync.Model.dll"
Import-Module -Name $LyncModelDll

START .\communicator.exe

#Get Lync Client State
$objLyncClient = [Microsoft.Lync.Model.LyncClient]::GetClient()
Write-Host = "State: " $objLyncClient.State

$objLyncClient.BeginInitialize($test,$null)
$objLyncClient.BeginSignIn($objLyncClient.Uri,$null,$null,$null,$null)

In the end I opted for an AutoIT script that starts Lync then hides the window and the taskbar icon.

;
; AutoIt Version: 3.0
; Language:       English
; Platform:       Win9x/NT
; Author:         Trentent Tye (trententtye@hotmail.com)
;
; Script Function:
;   Opens Lync in a hidden window and hides the tray icon.
;
#Include

Run("C:\Program Files (x86)\Microsoft Lync\communicator.exe", "", @SW_HIDE) ; 

WinWaitActive("Microsoft Lync")
Opt("WinTitleMatchMode", 4)
Global $hTray = WinGetHandle("[CLASS:Shell_TrayWnd]")
Global $hToolbar = ControlGetHandle($hTray, "", "[CLASSNN:ToolbarWindow321]")
Global $iCnt = _GUICtrlToolbar_ButtonCount($hToolbar)
;MsgBox(0, "Debug" , "Debug: $iCnt = " & $iCnt)
Global $iCmdVolume = -1
Global $sMsg, $sText, $iCmd
For $n = 0 To $iCnt - 1
    $sMsg = "Index: " & $n 
    $iCmd = _GUICtrlToolbar_IndexToCommand($hToolbar, $n)
    $sMsg &= "  CommandID: " & $iCmd
    $sText = _GUICtrlToolbar_GetButtonText($hToolbar, $iCmd)
    If StringInStr($sText, "Lync") Then $iCmdLync = $iCmd
    $sMsg &= "  Text: " & $sText
;    MsgBox(0, "Debug" , $sMsg)
Next
;MsgBox(0, "Debug","Debug: $iCmdLync = " & $iCmdLync)

_GUICtrlToolbar_SetButtonState($hToolbar, $iCmdLync, $TBSTATE_HIDDEN)


Monday, August 19, 2013

Windows Server 2012 R2 cache drive size for parity drives

It turns out the maximum a parity drive write cache size can be is 100GB.  I have a 500GB SSD (~480GB real capacity) so the maximum write cache size I can make for a volume is 100GB.  I suspect I maybe able to create multiple volumes and have each of them with a write cache of 100GB.  Until then this is the biggest it seems you can make for a single volume, so MS solves that issue of having a too large write cache.

Thursday, August 08, 2013

Testing Windows Storage Spaces Performance on Windows 2012 R2

Windows Storage Spaces parity performance on Windows Server 2012 is terrible.  Microsoft's justification for it is that it's not meant to be used for anything except "workloads that are almost exclusively read-based, highly sequential, and require resiliency, or workloads that write data in large sequential append blocks (such as bulk backups)."

I find this statement to be a bit amusing because trying to back up anything @ 20MB/sec takes forever.  If you setup a Storage Spaces parity volume at 12TB (available space) and you have 10TB of data to copy to it just to get it going it will take you 8738 seconds, or 145 hours, or 6 straight days.  I have no idea who thought anything like that would be acceptable.  Maybe they want to adjust their use case to volumes under 1GB?

Anyways, with 2012R2 there maybe some feature enhancements including a new feature for storage spaces; 'tiered storage' and write back caching.  This allows you to use fast media like flash to be  a staging ground so writes complete faster and then the writes to the fast media can transfer that data to the slower storage at a time that is more convient.  Does this fix the performance issues in 2012?  How does the new 2-disk parity perform?

To test I made two VM's.  One a generic 2012 and one a 2012R2.  They have the exact same volumes, 6x10GB volumes in total.  The volumes are broken down into 4x10GB volumes on a 4x4TB RAID-10 array, 1x10GB volume on a 256GB Samsung 840 Pro SSD and 1x10GB volume on a RAMDisk (courtesy of DataRAM).  Performance for each set of volumes is:

4x4TB RAID-10 -> 220MB/s write, 300MB/s read
256MB Samsung 840 Pro SSD -> ~250MB/s write, 300MB/s read
DataRAM RAMDisk -> 4000MB/s write, 4000MB/s read

The Samsung SSD volume has a small sequential write advantage, it should have a significant seek advantage, as well since the volume is dedicated on the Samsung it should be significantly faster as you could probably divide by 6 to get the individual performance of the 4x10GB volumes on the single RAID.  The DataRAM RAMDisk drive should crush both of them for read and write performance under all situations.  For my weak testing, I only tested sequential performance.

First thing I did was create my storage pool with my 6 volumes that reside on the RAID-10.  I used this powershell script to create them:
Get-PhysicalDisk
$disks = Get-PhysicalDisk |? {$_.CanPool -eq $true}
New-StoragePool -StorageSubSystemFriendlyName *Spaces* -FriendlyName TieredPool -PhysicalDisks $disks
Get-StoragePool -FriendlyName TieredPool | Get-PhysicalDisk | Select FriendlyName, MediaType
Set-PhysicalDisk -FriendlyName PhysicalDisk1 -MediaType HDD
Set-PhysicalDisk -FriendlyName PhysicalDisk2 -MediaType HDD
Set-PhysicalDisk -FriendlyName PhysicalDisk3 -MediaType HDD
Set-PhysicalDisk -FriendlyName PhysicalDisk4 -MediaType HDD
Set-PhysicalDisk -FriendlyName PhysicalDisk5 -MediaType HDD
Set-PhysicalDisk -FriendlyName PhysicalDisk6 -MediaType HDD

The first thing I did was create a stripe disk to determine my maximum performance amoung my 6 volumes.  I mapped to my DataRAM Disk drive and copied a 1.5GB file from it using xcopy /j

Performance to the stripe seemed good.  About 1.2Gb/s (150MB/s)

I then deleted the volume and recreated it as a single parity drive.

Executing the same command xcopy /j I seemed to be averaging around 348Mb/s (43.5MB/s)

This is actually faster than what I remember getting previously (around 20MB/s) and this is through a VM.

I then deleted the volume and recreated it as a dual parity drive.  To get the dual parity drive to work I actually had to add a 7th disk.  5 nor 6 would work as it would tell me I lacked sufficient space.

Executing the same command xcopy /j I seemed to be averaging around 209Mb/s (26.1MB/s)

I added my SSD volume to the VM and deleted the storage spaces volume.  I then added my SSD volume to the pool and recreated it with "tiered" storage now.

When I specified to make use the SSD as the tiered storage it removed my ability to create a parity volume.  So I created a simple volume for this testing.

Performance was good.  I achieved 2.0Gb/s (250MB/s) to the volume.



With the RAMDisk as the SSD tier I achieved 3.2Gb/s (400MB/s).  My 1.5GB file may not be big enough to ramp up to see the maximum speed, but it works.  Tiered storage make a difference, but I didn't try to "overfill" the tiered storage section.

I wanted to try the write-back cache with the parity to see if that helps.  I found this page that tells me it can only be enabled through PowerShell at this time.

$ssd_tier = New-StorageTier -StoragePoolFriendlyName TieredPool -FriendlyName SSD_Tier -MediaType SSD
$hdd_tier = New-StorageTier -StoragePoolFriendlyName TieredPool -FriendlyName HDD_Tier -MediaType HDD
$vd2 = New-VirtualDisk -StoragePoolFriendlyName TieredPool -FriendlyName HD -Size 24GB -ResiliencySettingName Parity -ProvisioningType Fixed -WriteCacheSize 8GB

I enabled the writecache with both my SSD and RAMDisk as being a part of the pool and the performance I got for copying the 1.5GB file was 1.8Gb/s (225MB/s)


And this is on a single parity drive!  Even though the copy completed quickly I could see in Resource Manager the copy to the E:\ drive did not stop, after hitting the cache at ~200MB/s it dropped down to ~45-30MB/s for several seconds afterwards.

You can see xcopy.exe is still going but there is no more network activity.  The total is in Bytes per second and you can see it's writing to the E: drive at about 34.13MB/s


I imagine this is the 'Microsoft Magic' going on where the SSD/write cache is now purging out to the slower disks.

I removed the RAMDisk SSD to see what impact it may have if it's just hitting the stock SSD.

Removing the RAMDisk SSD and leaving the stock SSD I hit about 800Mb/s (100MB/s).


This is very good!  I reduced the writecache size to see what would happen if the copy exceeded the cache...  I recreated the volume with the writecachesize at 100MB
$vd2 = New-VirtualDisk -StoragePoolFriendlyName TieredPool -FriendlyName HD -Size 24GB -ResiliencySettingName Parity -ProvisioningType Fixed -WriteCacheSize 8GB

As soon as the writecache filled up it was actually a little slower then before, 209Mb/s (26.1MB/s).  100MB just isn't enough to help.
100MB of cache is just not enough to help


Here I am now at the end.  It appears tiered storage only helps mirrored or stripe volumes.  Since they are the fastest volumes anyways, it appears the benefits aren't as high as they could be.  With parity drives though, the writecachesetting has a profound impact in the initial performance of the system.  As long as whatever fills the cache as enough time to purge to disk in the inbetweens you'll be ok.  By that I mean without a SSD present and write cache at default a 1GB file will copy over at 25MB/s in 40 seconds.  With a 100MB SSD cache present it will take 36 seconds because once the cache is full it will be bottlenecked by how fast it can empty itself.  Even worse, in my small scale test, it hurt performance by about 50%.  A large enough cache probably won't encounter this issue as long as there is sufficient time for it to clear.  Might be worthwhile to invest in a good UPS as well.  If you have a 100GB cache that is near full and the power goes out it will take about 68 minutes for the cache to finish dumping itself to disk.  At 1TB worth of cache you could be looking at 11.37 hours.  I'm not sure how Server 2012R2 deals with a power outage on the write cache, but since it's a part of the pool I imagine on reboot it will just pick up where it left off...?

Anyways, with storage spaces I do have to give Microsoft kudos.  It appears they were able to come close to doubling the performance on the single parity to ~46MB/s.  On the dual-parity it's at about 26MB/s under my test environment.  With the write cache everything is exteremely fast until the write cache becomes full.  After that it's painful.  So it's very important to size up your cache appropriately.  I have a second system with 4x4TB drives in a storage pool mirrored configuration.  Once 2012 R2 comes out I suspect I'll update to it and change my mirror into a single parity with a 500GB SSD cache drive.  Once that happens I'll try to remember to retest these performance numbers and we'll see what happens :)





Monday, July 22, 2013

“An error occurred while making the requested connection” - Citrix Web Interface

So I'm getting the dreaded “An error occurred while making the requested connection” while trying to launch some applications from our Citrix Web Interface.  It started happening suddenly but I'm tasked with figuring out why.  First thing I did was go to the Web Interface and check the event logs.  I found the following:



This wasn't much help, but I was able to narrow down that this was happening on one set of our servers that are split across two DC's.  One set of servers at BDC was fine, the other set of servers at ADC had a subset of servers that were not.  Doing a qfarm /load showed the problematic servers had no users on them at all, and no load evaluators were applied that would be causing our issue.

Logging into the server it was deteremined that it's DNS was registered to the wrong NIC (it was a PVS server that was multi-homed) and even worse for some of the servers, the NIC IP address was an old address and the new address wasn't even resolving!

For some reason it now appears our Windows 2008 servers are not registering their DNS on startup.  To resolve this issue for us we added a startup script with the simple command "ipconfig /registerdns" and within a few seconds the IP address is registered within DNS correctly and with the correct NIC.  We suspect that something is misconfigured at ADC as BDC does not have this issue nor does it need this tweak, but this is our work around until that is resolved.

Wednesday, July 17, 2013

Powershell script to compare Citrix Webinterface files

I've created a powershell script that will compare Citrix Webinterface files then export them out to a csv file.

#***************************************************************************************************************
#* Created by: Trentent Tye
#*         Intel Server Team
#*         IBM Canada Ltd.
#*
#* Creation Date: Jul 17, 2013
#*
#* File Name: parse-webinterfaceconf.ps1
#*
#* Description: This script will generate a CSV file of all the webinterface.conf files that you copy into
#*                  the same folder as this script.  For the purposes of our uses I have renamed the .conf files
#*                  to %URL%.conf.  This script will take the file name of the conf files to use as the CSV
#*                  headers then go through that file and compare if the uncommented values exist as compared
#*                  to the master "values.txt" file.  The "Values.txt" file was generated by taking the numerous
#*                  WebInterface files and deleting all lines that start with "#" and then removing duplicates
#*                  of all the values that were left.  For the 3 .confs I started with there are 160 values
#*                  amoung all three that have had a value set.
#*  
#*
#***************************************************************************************************************

$dirlist = Get-ChildItem -filter *.conf | Select-Object -ExpandProperty Name
#header value in powershell "Import-CSV" must be an array, a text variable parses as one header item
$header = @()
$header += "Value" 
foreach ($item in $dirlist) {
$header += $item 
}
#import csv with our header
$values = import-csv Values.txt  -Header $header


#do a foreach item in Values append the matching value in the other .conf files
foreach ($item in $values) {

  #check to see if the "Value" matches an item in the $dirlist and add that property in that file
  foreach ($diritem in $dirlist) {
  #get the webinterface.conf file
    get-content $diritem  | Foreach-Object {
    #check to see if one of the values in the "Value" file matches one of the values in the file that is NOT commented out
    if ($_.StartsWith($item.Value)) {
    #get the value to the right of the equal sign
    $pos = $_.IndexOf("=")
    $rightPart = $_.Substring($pos+1)
    #set the value to the appropriate column in the CSV
    $item.$diritem = $rightPart

      }
    } 
  }
}

$values | Export-CSV complete.csv -NoTypeInformation -force

Import-Csv -header $variable

I ran into an issue with Import-CSV where I was trying to pass a variable to the -header and it was failing pretty horribly, creating a single field instead of multiple fields.

I created a script to generate a CSV from Citrix WebInterface .conf files to compare the various web interfaces we have so that when we migrate users from the older interface to the newer interfaces we can properly communicate to them the changes they will experience.  In the course of developing this script I wanted to do a "dir *.conf" command and use that output as the header in the CSV.  Here is what I did originally:

$dirlist = Get-ChildItem -filter *.conf | Select-Object -ExpandProperty Name
#header value in powershell "Import-CSV" must be an array, a text variable parses as one header item
$header = "Value" 
foreach ($item in $dirlist) {
$header += $item 
}
#import csv with our header
$values = import-csv Values.txt  -Header $header

This failed.  Our $header variable became a single header in the CSV.  The help for "Import-CSV" says the -header should have a string value.  I tried $header.ToString() but it didn't work either.  I found you need to do the following:

$dirlist = Get-ChildItem -filter *.conf | Select-Object -ExpandProperty Name
#header value in powershell "Import-CSV" must be an array, a text variable parses as one header item
$header = @()
$header += "Value" 
foreach ($item in $dirlist) {
$header += $item 
}
#import csv with our header
$values = import-csv Values.txt  -Header $header

This makes an array then adds the appropriate values to the $header variable and the import-csv now has multiple columns.

Hurray!

Tuesday, July 16, 2013

Utilizing MCLI.exe to comment the newest version of a vDisk on Citrix Provisioning Services (PVS)

I've written this script to utilize MCLI.exe to add a comment to the newest version of a vDisk and have marked which fields correspond to what.

"C:\Program Files\Citrix\Provisioning Services\MCLI.exe" get diskversion -p disklocatorname=XenApp65Tn03 sitename=SHW storename=XenApp | FINDSTR /i /C:"version" > %TEMP%\diskver.txt

FOR /F "tokens=1-2 delims=: " %%A IN ('type %TEMP%\diskver.txt') DO set VERSIONN=%%B

"C:\Program Files\Citrix\Provisioning Services\MCLI.exe" set diskversion -p version=%VERSIONN% disklocatorname=XenApp65Tn03 sitename=SHW storename=XenApp -r description="Test"




This script can now be added to the "PVS Automatic" update feature to automatically comment the latest vDisk when it is updated.

Wednesday, July 10, 2013

Troubleshooting Audio issues in Citrix XenApp

We recently ran across an issue with XenApp 6.5 where we were publishing an application that required the "Beep" but it wasn't working.  The following is the troubleshooting steps I did to enable audio to work on that application.

First we created a Citrix policy to enable audio.  This policy looked like so:

We filtered on a user security group to enable the client audio redirection and added that filter group to the application.  From the original appearance of things, this should have been sufficient to enable client redirection.  But it did not.  So I wanted to verify that the policy was actually applying to the user account.  To do that, you login to the system with a user account and check in Regedit for the value "AllowAudioRedirection".  If it's set to 0x1 then the Citrix group policy has evaluated that client redirection should be enabled for your session.



Unfortunately, I still did not have audio redirection working...



Citrix advises that you can use dbgview.exe to further troubleshoot the Citrix Receiver to assist.  I launched dbgview.exe and started the trace and launched the application from the Webinterface.


"00000043 0.60113800 [7752] CAMSetAudioSecurity: Wd_FindVdByName failed"

CAM is a virtual channel (Virtual Channel Priority for Audio) and we can see it's failing.  I then used the Citrix ICA creator and launched the application using that.  The dbgview for that output looks like so:


00000013 0.44386363 [4496] CAMSetAudioSecurity: success

We can see that the audio virtual channel was able to successfully latch and I confirmed I had audio in the application.

From here the issue appeared to be when I launched the application from the webinterface or desktop shortcut.  I then compared the two ICA files, the one from the web interface and the one I created separately to see what was different.  The difference was glaringly obvious.  The working ICA file had "ClientAudio=On" and the broken one had "ClientAudio=Off".


Curious, I launched AppCenter and clicked through the applications settings and saw the following:



"Enable legacy audio" was unchecked.  I checked it and then logged off and logged back on the web interface and when I downloaded the ICA file, "ClientAudio=On" and I had audio.  I then unchecked that setting and confirmed it manipulated the ICA file as with it unchecked the ICA file generated had "ClientAudio=Off"

Who knows why it's called "legacy audio".  May as well just call that option "Enable audio" as that would be more accurate.  The Citrix documents on this setting says the following:

http://support.citrix.com/proddocs/topic/xenapp6-w2k8-admin/ps-sessions-en-dis-aud-pubapp-v2.html
To enable or disable audio for published applications
If you disable audio for a published application, audio is not available within the application under any condition. If you enable audio for an application, you can use policy settings and filters to further define under what conditions audio is available within the application.
  1. In the Delivery Services Console, select the published application for which you want to enable or disable audio, and select Action > Application properties. 
  2. In the Application Properties dialog box, click Advanced > Client options. Select or clear the Enable legacy audio check box.

Emphasis is mine.

Anyways, and now we have our applications with working audio and everything seems to be good again :)


To summarize the enable audio for a XenApp application you must:
1) Enable "legacy" audio
2) Enable a Citrix policy to configure audio redirection
3) Done.

Monday, July 08, 2013

Powershell script to manipulate "Register this connection's addresses in DNS"

We run a multihome NIC setup with our Citrix PVS servers and the "Provisioning" Network is a seperate VLAN that is only used by the PVS servers and goes no where.  Unfortunately, however, the "Provision" NIC can register itself in the DNS, causing devices outside of the Provisioning network (everyone) to resolve to the incorrect address.  To resolve this I ran this script across all my vDisks to remove the ability of the provision network to register itself as available in DNS.:


$provNic=Get-WmiObject Win32_NetworkAdapter -filter 'netconnectionid ="Provision"'
$prodNic=Get-WmiObject Win32_NetworkAdapter -filter 'netconnectionid ="Production"'
$adapters=Get-WmiObject Win32_NetworkAdapterConfiguration -filter 'IPEnabled=TRUE'
foreach($adapter in $adapters) {
  if ($prodNIC.name -eq $adapter.Description) {
    $adapter.SetDynamicDNSRegistration($true,$false)
  }
  if ($provNIC.name -eq $adapter.Description) {
    $adapter.SetDynamicDNSRegistration($false,$false)
  }
}

As you can see above, we have two NICs, "Provision" and "Production".  We do not want "Provision" registerted so it gets the $false,$false set, whereas "Production" gets $true,$false.

Tuesday, June 11, 2013

How to enable "Adaptive Display" in XenApp 6.5

Contrary to the documentation in the Group Policy settings for Citrix, XenApp requires the following settings configured for Adaptive Display to be enabled:

User settings
Minimum Image Quality
This setting specifies the minimum acceptable image quality for Adaptive Display. The less compression used, the higher the quality of images displayed. Choose from Ultra High, Very High, High, Normal, or Low compression.
By default, this is set to Normal.

Moving Image Compression
This setting specifies whether or not Adaptive Display is enabled. Adaptive Display automatically adjusts the image quality of videos and transitional slides in slide shows based on available bandwidth. With Adaptive Display enabled, users should see smooth-running presentations with no reduction in quality.
By default, this is set to Enabled.

Target Minimum Frame Rate
This setting specifies the minimum frame rate you want. The minimum is a target and is not guaranteed. Adaptive Display automatically adjusts to stay at or above this setting where possible.
By default, this is set to 10 frames per second.

Progressive Compression Level
Set to Disabled

Even though the GPO's state these only apply to XenDesktop, they also apply to XenApp and can be confirmed if you publish HDX Monitor 3.0 on a XenApp server and monitor the ICA session, you can see the transient quality increasing or decreasing depending on your scenario.

Friday, May 31, 2013

SCVMM: Install VM components Failed

I'm attempting to deploy a virtual machine from a template but I get this error: Install VM components: Failed.




Error (2940)
VMM is unable to complete the requested file transfer. The connection to the HTTP server 2012-SCVMM.bottheory.local could not be established.
Unknown error (0x80072ee2)

Recommended Action
Ensure that the HTTP service and/or the agent on the machine 2012-SCVMM.bottheory.local are installed and running and that a firewall is not blocking HTTP/HTTPS traffic on the configured port.



TL;DR
SO...  Long story short; if you are encountering this error, I would suggest booting your VHD file in a VM and re-sysprep /generalize it.  If you've maxed out on sysprep's I have a post earlier in my blog on how to get around the 3-times limit and rerun sysprep.  Alternatively, you can try what I did, but I can't gaurantee your success and replace the BCD file in your Library VHD with a BCD you *know* has been sysprepp'ed and try redeploying it.
=------------------------------------------------------------------------------------------------=

I've ensured that HTTP and HTTPS is not blocked (firewall is disabled) and the agent on my SCVMM machine is installed and running.  So this error message is somewhat useless.



So I took my two boxes, 2012-SCVMM (the SCVMM server) and S5000VXN-SERVER (the Hyper-V host) and procmon'ed them while it was attempting to "Install VM Components" to try and understand what it's trying to do.



After reinitating the task, we can see that the vmmAgent.exe on the Hyper-V host accesses the file about 2 seconds after I submit the command to retry the job.

A few seconds after this, it appears to "CreateFile" in the Windows\Temp directory; but this is not actually correct.



What it is really doing is *mounting* the VHD into a folder in the Windows\Temp directory.  You can actually watch this in action if you view Disk Management on the Hyper-V host while you execute the task.




So now that we know it's mounting the file as a volume this helps us narrow down on what Hyper-V is attempting to do...  And I suspect what it is attempting to do is "offline servicing" of the attached vdisk/vhd.



After attaching the vdisk the next thing it does it query the BCD file on the system.  Maybe it needs to be in a certain mode to operate?  I'm not sure...



Continuing on we can see that events are written to the Microsoft-Windows-FileShareShadowCopyProvider Operational.evtx, System.evtx, and Microsoft-Windows-Bits-CompactServer Operational.evtx event logs.  Examining each log at the time stamp showed the FileShareShadowCopyProvider and System log were just noticed of volume shadow copy starting, but the BITS event log was interesting.






It showed that it was doing something with the BCD file.  The Hyper-V host was *serving* it out.  I suspect it was serving it to the SCVMM.
Looking back at the SCVMM server we see that was executing some WinRM commands.  Sadly, we do not know what commands it was trying to send.

If I mount the vhd file and check out the BCD file I can see that it appears to be corrupted in that it doesn't know what the proper boot device should be.

C:\Windows\system32>bcdedit /store "K:\boot\bcd" /enum all

Windows Boot Manager
--------------------
identifier              {bootmgr}
device                  unknown
description             Windows Boot Manager
locale                  en-US
inherit                 {globalsettings}
bootshutdowndisabled    Yes
default                 {default}
resumeobject            {b520e13c-48da-11e2-9a8b-00155d011700}
displayorder            {default}
                        {7619dcc9-fafe-11d9-b411-000476eba25f}
toolsdisplayorder       {memdiag}
timeout                 3

Windows Boot Loader
-------------------
identifier              {7619dcc9-fafe-11d9-b411-000476eba25f}
device                  ramdisk=[boot]\sources\boot.wim,{7619dcc8-fafe-11d9-b411
-000476eba25f}
path                    \windows\system32\boot\winload.exe
description             Windows Setup
locale                  en-US
inherit                 {bootloadersettings}
osdevice                ramdisk=[boot]\sources\boot.wim,{7619dcc8-fafe-11d9-b411
-000476eba25f}
systemroot              \windows
detecthal               Yes
winpe                   Yes
ems                     Yes

Windows Boot Loader
-------------------
identifier              {default}
device                  unknown
path                    \Windows\system32\winload.exe
description             Windows Server 2012
locale                  en-US
inherit                 {bootloadersettings}
allowedinmemorysettings 0x15000075
osdevice                unknown
systemroot              \Windows
resumeobject            {b520e13c-48da-11e2-9a8b-00155d011700}
nx                      OptOut
detecthal               Yes

Resume from Hibernate
---------------------
identifier              {b520e13c-48da-11e2-9a8b-00155d011700}
device                  unknown
path                    \Windows\system32\winresume.exe
description             Windows Resume Application
locale                  en-US
inherit                 {resumeloadersettings}
allowedinmemorysettings 0x15000075
filepath                \hiberfil.sys

Windows Memory Tester
---------------------
identifier              {memdiag}
device                  unknown
path                    \boot\memtest.exe
description             Windows Memory Diagnostic
locale                  en-US
inherit                 {globalsettings}
badmemoryaccess         Yes

EMS Settings
------------
identifier              {emssettings}
bootems                 Yes

Debugger Settings
-----------------
identifier              {dbgsettings}
debugtype               Serial
debugport               1
baudrate                115200

RAM Defects
-----------
identifier              {badmemory}

Global Settings
---------------
identifier              {globalsettings}
inherit                 {dbgsettings}
                        {emssettings}
                        {badmemory}

Boot Loader Settings
--------------------
identifier              {bootloadersettings}
inherit                 {globalsettings}
                        {hypervisorsettings}

Hypervisor Settings
-------------------
identifier              {hypervisorsettings}
hypervisordebugtype     Serial
hypervisordebugport     1
hypervisorbaudrate      115200

Resume Loader Settings
----------------------
identifier              {resumeloadersettings}
inherit                 {globalsettings}

Device options
--------------
identifier              {7619dcc8-fafe-11d9-b411-000476eba25f}
ramdisksdidevice        boot
ramdisksdipath          \boot\boot.sdi

It's not actually corrupted though; the reason why the devices are unknown is because bcdedit isn't finding the disk signature of the volume I mounted.  But it has the disk signature because I can boot with it without issue.

Continuing on...

In order to try and find out what commands WSMAN was sending I enabled debugview on the SCVMM server:
http://support.microsoft.com/kb/970066?wa=wsignin1.0

I then reproduced the error by rerunning the Create Virtual Machine job.



DebugView gave me more information to narrow down what was happening.  It appears that the process is failing with:
[4276] 10B4.000C::05/31-12:52:00.894#16BcdUtil.cs(1987): bootDevice UnknownDevice
[4276] 10B4.000C::05/31-12:52:01.334#16MountedVhd.cs(91): MountedVhd windows volume not found
[4276] 10B4.000C::05/31-12:52:01.342#16VMAdditions.cs(874): VMAdditions install failed at OS detection phase for vm AirVid
[4276] 10B4.000C::05/31-12:52:01.388#16VMAdditions.cs(874): Microsoft.VirtualManager.Engine.VmOperations.MountedSystem+BootOrSystemVolumeNotFoundException: Virtual Machine Manager cannot locate the boot or system volume on virtual machine NO_PARAM. The resulting virtual machine might not start or operate properly. 

Doing some googling on this took me to a Korean Microsoft page where the following was stated:
http://social.technet.microsoft.com/Forums/ko-KR/momsmsmofko/thread/fedbc514-1fc0-4b55-979b-7d07babb074b/

When creating a new VM from template, and that template contains a blank VHD or a non-Windows OS or a Windows OS that has not been generalized (i.e. the OS is not sysprepped), then the job will fail because VMM expects a VM from template to go through the sysprep customization process (hence why we ask for an OS profile). VMM will crack open the VHD and check of the OS is in a sysprep state. If not, then the job will fail. To create a proper template, you can use an existing Vm with a running Windows OS (right click on it and select New Template… this will kick off sysprep in the OS and then store the VM to Library as template… this removes the original VM). Or… if you already have VHDs that have been sysprepped, simply import them to the Library and then when you create a new template… attach that VHD instead of the blank one. Last… you can create a new template that does not require a sysprepped OS by selecting ‘Customization Not Required’ from the Guest OS profile dropdown:
http://blogs.technet.com/b/hectorl/archive/2008/08/21/digging-deeper-into-error-13206-virtual-machine-manager-cannot-locate-the-boot-or-system-volume-on-virtual-machine.aspx

Thinking about it, I do not think my VHD was SysPrep'ed.  I find it interesting that sysprep appears to do something to the BCD file.  To find out what sysprep does to the BCD file I booted up my 2012 VHD into Windows and ran sysprep, generalize and shutdown.


With the VM now generalized I can crack open the VHD and see what's added to the BCD to make it so special...

C:\Users\amttye\Desktop>fc before-sysprep.txt after-sysprep.txt
Comparing files before-sysprep.txt and AFTER-SYSPREP.TXT
***** before-sysprep.txt
identifier              {bootmgr}
device                  partition=K:
description             Windows Boot Manager
***** AFTER-SYSPREP.TXT
identifier              {bootmgr}
device                  locate=unknown
description             Windows Boot Manager
*****

***** before-sysprep.txt
toolsdisplayorder       {memdiag}
timeout                 3

***** AFTER-SYSPREP.TXT
toolsdisplayorder       {memdiag}
timeout                 30

*****

***** before-sysprep.txt
identifier              {default}
device                  partition=K:
path                    \Windows\system32\winload.exe
***** AFTER-SYSPREP.TXT
identifier              {default}
device                  locate=\Windows\system32\winload.exe
path                    \Windows\system32\winload.exe
*****

***** before-sysprep.txt
inherit                 {bootloadersettings}
allowedinmemorysettings 0x15000075
osdevice                partition=K:
systemroot              \Windows
***** AFTER-SYSPREP.TXT
inherit                 {bootloadersettings}
recoveryenabled         No
allowedinmemorysettings 0x15000075
osdevice                locate=\Windows
systemroot              \Windows
*****

***** before-sysprep.txt
nx                      OptOut
detecthal               Yes

***** AFTER-SYSPREP.TXT
nx                      OptOut

*****

***** before-sysprep.txt
identifier              {b520e13c-48da-11e2-9a8b-00155d011700}
device                  partition=K:
path                    \Windows\system32\winresume.exe
***** AFTER-SYSPREP.TXT
identifier              {b520e13c-48da-11e2-9a8b-00155d011700}
device                  locate=\Windows\system32\winresume.exe
path                    \Windows\system32\winresume.exe
*****

***** before-sysprep.txt
inherit                 {resumeloadersettings}
allowedinmemorysettings 0x15000075
filepath                \hiberfil.sys

***** AFTER-SYSPREP.TXT
inherit                 {resumeloadersettings}
recoveryenabled         No
allowedinmemorysettings 0x15000075
filedevice              locate=\hiberfil.sys
filepath                \hiberfil.sys
debugoptionenabled      No

*****

***** before-sysprep.txt
identifier              {memdiag}
device                  unknown
path                    \boot\memtest.exe
***** AFTER-SYSPREP.TXT
identifier              {memdiag}
device                  locate=\boot\memtest.exe
path                    \boot\memtest.exe
*****

Interestinginly the differences appear to be mostly "locate=%%".  I guess this would make sense as the assumption is the BCD is being moved to a new disk with a new disk signature and so it can't lock on to the existing signature.  Some other oddities is the removal of "detecthal" and explicit declarations of debugoptionenabled and recoveryenabled.  I suspect that a generalized BCD file is portable, so I'm going to extract it from this image and inject it into my previously "failing" image.  I then edited the template and removed the old VHD and added the new one.



And..........?  Lets go to DebugView:

[4276] 10B4.000C::05/31-13:57:40.193#16SystemInformation.cs(192): SYSTEM: :\ Version=0.0 HALType=  Memory=2MB, Procs=1 Is64s=False . OSLanguage=0
[4276] 10B4.000C::05/31-13:57:40.193#16SystemInformation.cs(970): Lookup for '\??\Volume{b42a3001-c871-11e2-93f4-0015172fc019}' in MountedDevices
[4276] 10B4.000C::05/31-13:57:40.193#16SystemInformation.cs(764): DISK: 10, signature=a9083c0a #partitions 1
[4276] 10B4.000C::05/31-13:57:40.194#16SystemInformation.cs(768):  PARTITION: 10.0 Bootable=True, #LDs 1
[4276] 10B4.000C::05/31-13:57:40.194#16SystemInformation.cs(775):   LOGICALDRIVE: \\?\Volume{b42a3001-c871-11e2-93f4-0015172fc019}\(\\?\Volume{b42a3001-c871-11e2-93f4-0015172fc019}\), WindowsDrive=True, IsBoot=False, BootSectorType=Bootmgr, FullSize=136363114496, [1048576-136364163072]
[4276] 10B4.000C::05/31-13:57:40.194#16MountedVhd.cs(623): FindBootVol \\?\Volume{b42a3001-c871-11e2-93f4-0015172fc019}\, found boot drive candidate
[4276] 10B4.000C::05/31-13:57:40.194#16MountedVhd.cs(308): Bootmgr boot loader
[4276] 10B4.000C::05/31-13:57:40.194#16MountedVhd.cs(1053): Trying to get the mounted point for volume \\?\Volume{b42a3001-c871-11e2-93f4-0015172fc019}\.
[4276] 10B4.000C::05/31-13:57:40.196#16CommonUtils.cs(171): Fixup & Copy 'C:\Windows\TEMP\tmp2E1.tmp\boot\bcd' to C:\Users\svc_scvmm\AppData\Local\Temp\tmpA2F4.tmp
[4276] 10B4.000C::05/31-13:57:40.196#04BitsDeployer.cs(1392): Deploy file C:\Windows\TEMP\tmp2E1.tmp\boot\bcd from s5000vxn-server.bottheory.local to C:\Users\svc_scvmm\AppData\Local\Temp\tmpA2F4.tmp on 2012-SCVMM.bottheory.local





Voila!  It appears much better than before.  It found the drive correctly and checked the BCD file and found it is the "Generalize" state.  The *actual* image I originally made was NOT sysprep'ed and all I did was replace the BCD file, but it allowed it to continue beyond and the machine actually completed the imaging process properly.  It joined the domain and whatever else the answer file was I gave it in SCVMM.

It appears I was correct in my earlier assumption about what SCVMM is doing.  It goes and grabs the BCD file and transfers it from the target machine to itself, "fixes it up" (not sure what it's doing at this stage precisely), then sends it back for injection.  Certainly a bit of a complicated process with a fair bit that can go wrong, but shame on Microsoft for having such a poor error message.  I suspect it wouldn't have required much effort to push out a real message; something to the effect of, "The BCD of this vDisk does not appear to have been through the sysprep /generalize process.  Please rerun sysprep against the image and try again".

SO...  Long story short; if you are encountering this error, I would suggest booting your VHD file in a VM and re-sysprep /generalize it.  If you've maxed out on sysprep's I have a post earlier in my blog on how to get around the 3-times limit and rerun sysprep.  Alternatively, you can try what I did, but I can't gaurantee your success and replace the BCD file in your Library VHD with a BCD you *know* has been sysprepp'ed and try redeploying it.