{"id":2211,"date":"2013-11-10T02:59:54","date_gmt":"2013-11-10T02:59:54","guid":{"rendered":"http:\/\/www.atumvirt.com\/?p=2211"},"modified":"2013-11-10T02:59:54","modified_gmt":"2013-11-10T02:59:54","slug":"dramatically-reducing-logon-time-to-desktop-by-moving-from-group-policy-preferences-to-powershell-logon-script","status":"publish","type":"post","link":"https:\/\/avtempwp.azurewebsites.net\/2013\/11\/dramatically-reducing-logon-time-to-desktop-by-moving-from-group-policy-preferences-to-powershell-logon-script\/","title":{"rendered":"Dramatically Reduce Logon “Time to Desktop” By Moving From Group Policy Preferences to Powershell Logon Script"},"content":{"rendered":"

Update 5\/16\/2017: Thanks to tabularasa for some improvements to this, fixing some missing slashes among other things!<\/strong>
\nIn years past, logon scripts were the bane of many logon experiences due to their synchronous nature. \u00a0Beginning with Windows 7, logon scripts can be processed asynchronously<\/a>. \u00a0We glossed over this fact: “Nice feature, but how will it help us? We’re heavily invested into GP Preferences now!” \u00a0However, now that we’re 1078 virtual desktops deep, logon time is a serious concern. \u00a0Citrix has failed to provide us with a way to extend the time on the automatic “log off if log on takes more than 90 seconds” feature. \u00a0In most cases, this isn’t an issue, but this limitation highlighted a serious flaw: \u00a0Even under normal circumstances, there are some cases where some users might exceed 90s.<\/p>\n

Our organization uses the HP Universal Print driver, Ricoh Universal Print driver and the Lexmark Universal print driver and we map printers with Group Policy Preferences. \u00a0This mapping process takes, on an “average” printer, 6-7 seconds. \u00a0Unfortunately, this processing does in fact run synchronously (possibly due to GPP Drive mapping – I just found this article <\/a>but it requires further testing! \u00a0Would be interesting if this whole mess could be avoided by moving drive mapping out of GPO!) and therefore can add substantial time to the logon process. \u00a0Some users have 5-15 printers available to them, bringing the practical limit to the number of printers to something like 10 (given that portions of the logon process require some time).<\/p>\n

As we are heavily invested in preferences, item level targeting and (as everyone is) short on staff \/ training, we decided to leverage that investment in GPP but still gain the benefits of the logon script. \u00a0A colleague of mine, Matt Heckman, created a rather ingenious Powershell script that effectively uses GPP Printer entries from a “dummy” GPO to map printers for our VDI environment. \u00a0This means that our level 1 admins can still create printers using GPP with simple filters and the users in VDI will automatically map them. \u00a0The results were substantial: \u00a0A logon that took 60 seconds to reach a desktop was shortened by 45 seconds! \u00a0In K-12, logon time is king as it directly translates into instructional time.<\/p>\n

So, how does it work?<\/p>\n

Create a new GPO. \u00a0It is not required to link it anywhere in the domain. \u00a0Be sure to clearly name it so that someone doesn’t think that it is an unused GPO!<\/p>\n

Copy your existing printer preferences into the new GPO.<\/p>\n

Modify the logon script to reflect the name\/GUID of your new GPO. \u00a0To obtain the GUID, in GPMC \u00a0click the “details” tab, then copy the “Unique ID”. If you use any filters other than computer name or security group name you will need to extend the script.<\/p>\n

Without further delay, give it a whirl!<\/p>\n

 <\/p>\n

#$GPOGuid = \"{7B4605FA-3435-4A2B-8A33-A101944C5E47}\"\n$GPOGuidObject = Get-GPO \"VOA - Printer GPP Policy\"\n$GPOGuid = $GPOGuidObject.Id\n$GPOGuidDomain = $GPOGuidObject.DomainName\n$LogDir=\"H:\\Windows\\PrinterLogs\"\n$LogPath=\"$($LogDir)\\$($env:USERNAME)_$($env:COMPUTERNAME)_LogonScript.log\"\nwrite-host \"Writing logs to $($LogPath)\"\n \n#Check to see if the log directory exists and create it it doesn't yet exist.\n    #$LogPathExist=false\n    #Is the H drive already mapped\n    $LogPathExist=Test-Path H:\n    if ($LogPathExist) {\n           write-host \"H Drive Exists\"\n    } else {\n        write-host \"H Drive not Mapped\"\n        return\n    }\n    $LogPathExist=Test-Path $LogDir\n    if ($logPathExist) {\n        write-host \"$($LogDir) Exists\"\n    } else {\n        write-host \"$($LogDir) Does not exist\"\n        #create directory for for logfile\n        mkdir $LogDir\n    }\n \n \n    #Is the folder created\n        #create the folder if its not already there\n \n \n \n \nStart-Transcript -Path $LogPath\n \n[xml]$printersXml = Get-Content \"\\\\$GPOGuidDomain\\sysvol\\$GPOGuidDomain\\Policies\\{$GPOGuid}\\User\\Preferences\\Printers\\Printers.xml\"\n \n$userGroups = ([Security.Principal.WindowsIdentity]\"$($env:USERNAME)\").Groups.Translate([System.Security.Principal.NTAccount])\n$computerGroups = ([Security.Principal.WindowsIdentity]\"$($env:COMPUTERNAME)\").Groups.Translate([System.Security.Principal.NTAccount])\n \nFunction Process-FilterComputer {\n    Param(\n        $filter\n    )\n \n    $result = $false\n \n    if ($filter.type -eq \"NETBIOS\") {\n        if ($env:COMPUTERNAME -like $filter.name) {\n            $result = $true\n        }\n    }\n \n    if ($filter.not -eq 0) {\n        return $result\n    } else {\n        return !$result\n    }\n}\n \nFunction Process-FilterUser {\n    Param(\n        $filter\n    )\n \n    $result = $false\n \n    if (\"$env:USERDOMAIN\\$env:USERNAME\" -like $filter.name) {\n        $result = $true\n    }\n \n    if ($filter.not -eq 0) {\n        return $result\n    } else {\n        return !$result\n    }\n}\n \nFunction Process-FilterGroup {\n    Param(\n        $filter\n    )\n \n    $result = $false\n \n    if ($filter.userContext -eq 1) {\n        if ($userGroups -contains $filter.name) {\n            $result = $true\n        }\n    } else {\n        if ($computerGroups -contains $filter.name) {\n            $result = $true\n        }\n    }\n \n    if ($filter.not -eq 0) {\n        return $result\n    } else {\n        return !$result\n    }\n}\n \nFunction Process-FilterCollection {\n    Param(\n        $filter\n    )\n \n    if ($filter.HasChildNodes) {\n        $result = $true\n        $childFilter = $filter.FirstChild\n \n        while ($childFilter -ne $null) {\n            if (($childFilter.bool -eq \"OR\") -or ($childFilter.bool -eq \"AND\" -and $result -eq $true)) {\n                if ($childFilter.LocalName -eq \"FilterComputer\") {                    \n                    $result = Process-FilterComputer $childFilter\n                } elseif ($childFilter.LocalName -eq \"FilterUser\") {\n                    $result = Process-FilterUser $childFilter\n                } elseif ($childFilter.LocalName -eq \"FilterGroup\") {\n                    $result = Process-FilterGroup $childFilter\n                } elseif ($childFilter.LocalName -eq \"FilterCollection\") {\n                    $result = Process-FilterCollection $childFilter\n                }\n \n                #Write-Host \"Process-$($childFilter.LocalName) $($childFilter.name): $($result)\"\n            } else {\n                #Write-Host \"Process-$($childFilter.LocalName) $($childFilter.name): skipped\"\n            }\n \n            if (($childFilter.NextSibling.bool -eq \"OR\") -and ($result -eq $true)) {\n                break\n            } else {\n                $childFilter = $childFilter.NextSibling\n            }\n        }\n    }\n \n    if ($filter.not -eq 1) {\n        return !$result\n    } else {\n        return $result\n    }\n}\n \n$com = New-Object -ComObject WScript.Network\n \n$installedPrinterDrivers = get-printerdriver\n \n#Get-Content \"\\\\$GPOGuidDomain\\sysvol\\$GPOGuidDomain\\Policies\\{$GPOGuid}\\User\\Preferences\\Printers\\Printers.xml\"\n \nforeach ($sharedPrinter in $printersXml.Printers.SharedPrinter) {\n    $filterResult = Process-FilterCollection $sharedPrinter.Filters\n    Write-Host \"$($sharedPrinter.name) filters passed: $($filterResult)\"\n \n    if ($filterResult -eq $true) {\n        if ($sharedPrinter.Properties.action -eq 'C') {\n            #check to see if the driver is present on the XenAppServer\n           \n           $printServer = $sharedPrinter.Properties.path\n           $printServer = $printServer.Split(\"\\\\\")[2]\n           $driverName = Get-Printer -ComputerName $($PrintServer) -Name $($sharedPrinter.name)\n \n            if ($installedPrinterDrivers | where {$_.name -eq $driverName.drivername}) {\n            #Create the printer in the session\n                $com.AddWindowsPrinterConnection($sharedPrinter.Properties.path)\n                \"AddWindowsPrinterConnection:$($sharedPrinter.Properties.path)\"\n                if ($sharedPrinter.Properties.default -eq 1) {\n                    #$com.SetDefaultPrinter($sharedPrinter.Properties.path)\n                    (Get-WmiObject -ComputerName . -Class Win32_Printer -Filter \"Name='$($($sharedPrinter.Properties.path) -replace \"\\\\\",\"\\\\\")'\").SetDefaultPrinter()\n                    \"SetDefaultPrinter:$($sharedPrinter.Properties.path)\"\n                }\n            } else {\n            #alert that the driver isn't present\n            write-host \"`r`n  The driver for $($sharedPrinter.Properties.path) doesn't appear to be present.  The driver that needs to be installed is $($driverName.drivername).  Please install this driver on the XenApp Server.  `n`r\"\n            }\n        } elseif ($sharedPrinter.Properties.action -eq 'D') {\n            $com.RemovePrinterConnection($sharedPrinter.Properties.path, $true, $true)\n            \"RemovePrinterConnection:$($sharedPrinter.Properties.path)\"\n        }\n    }\n}\n \nStop-Transcript<\/pre>\n","protected":false},"excerpt":{"rendered":"

Update 5\/16\/2017: Thanks to tabularasa for some improvements to this, fixing some missing slashes among other things! In years past, logon scripts were the bane of many logon experiences due to their synchronous nature. \u00a0Beginning with Windows 7, logon scripts can be processed asynchronously. \u00a0We glossed over this fact: “Nice feature, but how will it […]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[10,40,77],"tags":[86,103],"_links":{"self":[{"href":"https:\/\/avtempwp.azurewebsites.net\/wp-json\/wp\/v2\/posts\/2211"}],"collection":[{"href":"https:\/\/avtempwp.azurewebsites.net\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/avtempwp.azurewebsites.net\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/avtempwp.azurewebsites.net\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/avtempwp.azurewebsites.net\/wp-json\/wp\/v2\/comments?post=2211"}],"version-history":[{"count":0,"href":"https:\/\/avtempwp.azurewebsites.net\/wp-json\/wp\/v2\/posts\/2211\/revisions"}],"wp:attachment":[{"href":"https:\/\/avtempwp.azurewebsites.net\/wp-json\/wp\/v2\/media?parent=2211"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/avtempwp.azurewebsites.net\/wp-json\/wp\/v2\/categories?post=2211"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/avtempwp.azurewebsites.net\/wp-json\/wp\/v2\/tags?post=2211"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}