weepingapps

App Packaging, App Deployment, Deployment Tools, OS Deployment

Windows 10 Start Layout Customization Not Working

There are many blog articles about customizing the start menu layout for Windows 8 and Windows 10.

http://stealthpuppy.com/customizing-the-windows-8-1-start-screen-dont-follow-microsofts-guidance/

http://www.kapilarya.com/specify-fixed-layout-start-menu-in-windows-10

https://msdn.microsoft.com/en-us/library/windows/hardware/mt171092%28v=vs.85%29.aspx#supported_unattend_settings_for_start

These are great. However, I and some others were not having much luck getting it to work. But determined to make it work I got setup with my favourite tools, Procmon and EventViewer.

Here is the exact scenario I was using:

  1. Use MDT to build and Capture Windows 10 Enterprise.
  2. Use Unattend.xml CopyProfile parameter to customize the default profile.
  3. During my Deploy Task Sequence in Configuration Manager, run the command ‘Import-StartLayout.. etc

However, any user who logged on was not getting the customized start screen. It seems very hard to get verbose/debugging from the the new shell experience and I couldn’t find much in the Event Log including the Debug and Analytical logs.

I was about to try viewing the internals of the TileLayerDatabase .edb files when it dawned on me that CopyProfile has brought the Administrators TileLayerDatabase (Which are not transportable) files into the default profile.

Simply deleting or renaming this folder and BAM! Start menu layout customizations are working.

Now I will simply amend my deploy task sequence to delete this folder.

Capture

Advertisements

Install all Print Drivers from Print Cluster

Hi Ho!

It has been quite some time since I last posted. I have packaged many apps and written much Powershell. But I tend not to post much of it online because I can’t find the time to _write_ about it.

Well, I’ve decided that it may help someone if I post the code even without much comment. I feel that my code is fairly readable anyway.

This one was written because since my last post I am now a Citrix Admin. As such I wanted to add all our print drivers to the master PVS image in one fell swoop.

Jan Egil Ring published a script to do this a few years back, but it only works on non-clustered print servers. So I’ve re-written it to work with clustered ones.

$ClusterName = 'wsp-staffpnt01'

function Get-ClusterPrinters {
Param($CluserName)
    $RemoteReg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine",$ClusterName)
    $ClusterKey = 'Cluster\Resources'
    #$RemoteReg.opensubkey($ClusterKey)
    $ClusterResourcesBase = $RemoteReg.OpenSubKey($ClusterKey)
    $ClusterResourcesBase.GetSubKeyNames() | %{
        $ClusterResource = $ClusterResourcesBase.OpenSubKey("$_")
        If ($ClusterResource.GetValue('Type') -eq 'Print Spooler') {
            #Get Printers
            $PrinterKeys = $ClusterResource.OpenSubKey('Parameters\Printers')
            $PrinterKeys.GetSubKeyNames() | %{
                $DsSpooler = $PrinterKeys.OpenSubKey("$_\DsSpooler")
                [PSCustomObject]@{
                    'PrinterName' = $DsSpooler.GetValue('printerName')
                    'DriverName' = $DsSpooler.GetValue('driverName')
                    'UNCName' = $DsSpooler.GetValue('uNCName')
                }
            }
        }
    }
}
$AllPrinters = Get-ClusterPrinters -CluserName $ClusterName
$Drivers = $AllPrinters | Select-Object -Unique DriverName
$Printers = @()
ForEach ($Driver in $Drivers) {
    $Printers += $AllPrinters | Where-Object {$_.DriverName -eq $Driver.DriverName} | Select -First 1
}

$LocalDrivers = Get-WmiObject -Class Win32_PrinterDriver | ForEach-Object {$_.Name.Split(',')[0]}
$CurrentPrinter = 1
ForEach ($Printer in $Printers) {
    Write-Progress -Activity 'Installing Printers...' -Status "Current printer: $($Printer.PrinterName)" -Id 1 -PercentComplete (($CurrentPrinter/$Printers.Count) * 100)
    $LocallyInstalled = $LocalDrivers | Where-Object {$_ -eq $Printer.DriverName}
    If (-Not $LocallyInstalled) {
        Write-Verbose "$($Printer.DriverName) is not installed locally"
        $AddPrinterConnection = Invoke-WmiMethod -Path Win32_Printer -Name AddPrinterConnection -ArgumentList ($Printer.UNCName) -EnableAllPrivileges
        $ReturnCode = $AddPrinterConnection.ReturnValue
    } Else {
        Write-Verbose "$($Printer.DriverName) is installed locally"
        $ReturnCode = 'Already Installed'
    }
    [PSCustomObject]@{
        'DriverName' = $Printer.DriverName
        'ReturnCode' = $ReturnCode
    }
    $CurrentPrinter ++
}

$Printers = Get-WmiObject -Class Win32_Printer -EnableAllPrivileges -Filter network=true
If ($Printers) {
    ForEach ($Printer in $Printers) {
        $Printer.Delete()
    }
}

Script Convert and Import AppV 4.6 apps to 5.0 SP1 – Gather

There are probably dozens of articles with simple scripts on this process on the web and truth be told it is an easy script to write.
But hey, I still should share:

There are 3 main steps to preparing and doing this action.

1. Gather your sources.
2. Analyse the packages.
3. Convert the packages.

Gathering Sources:
APPV 4.6 Infrastructure mode users probably won’t have any problems with this stage (to my understanding), however if you use SCCM integration you will be familiar with the fact that when you import an APPV 4.6 package to SCCM 2007, it prompts you for the source APPV files, then the destination APPV files which will be used as the source from here on out.
You cannot use the SCCM 2007 APPV Package source to Analyse or convert your packages, you must have retained your original packages.

Luckily our packaging process has us retain our source packages and I was able to pull them together with this Powershell code:

PS> New-Item -ItemType File -Path \\appdev\SCCMSoftware\APPVAnalysis.txt
PS> Get-ChildItem -Path \\appdev\SCCMSoftware -Filter *.sprj -Recurse | ForEach-Object {
Split-Path -Path $_.FullName -Parent} | Add-Content -Path \\Appdev\SCCMSoftware\APPVAnalysis}

This will Generate a file APPVAnalysis.txt which contains a list of all your sprj files recursively from the \\appdev\SCCMSoftware path.

After I had gathered my data I had a list of about 238 APPV packages.
I then decided a cleanup was in order and removed duplicates, old revisions and retired applications from my list bringing the list down to about 152.

The next day or two were spent gathering information about each package and documenting them in a CSV files. The data I gathered was:
Name, Manufacturer, Version, Language, Source, ADGroup, Description, Type, Group, GroupName
Name: Name of the application, ie Adobe Acrobat
Manufacturer: Name of the vendor, ie Adobe
Language: Language of the installer, ie APPV
Source: Source path gathered from previous step, ie \\appdev\SCCMSoftware\Adobe\Acrobat\11\APPV
Version: Version of the package/source, ie 11.0.3
Description: Licensing (We use, Open|Site|Restricted), ie Open
Type: Action to perform later (Import|Manual|Retire), ie Import
Group: Type of collection query (Comma seperated USR,WKS), ie USR,WKS
GroupName: Corresponding AD Group used for the collection query, ie SCCM Adobe Adobe Acrobat 11.0.3 APPV WKS

Our naming conventions haven’t been too tight over the years so I could not script that process. Alot of the data we store in SCCM 2007 package (We populate the license type in the description of the package), so I did export that detail from SCCM to populate my CSV.

Next post – Analyse the packages

Script Convert and Import AppV 4.6 apps to 5.0 SP1 – Overview

So at my University we have commenced a new project.
A brief overview of where we are today, where we want to be and then how to get there.

Where We are.
OS/Updates/Applications are all deployed using Config Manager 2007 SP2 R3.
We have a mix of APPV 4.6, MSI EXE and Custom VBS scripts to deploy applications.
We use two Task Sequences, One to build a core image and one to deploy it.
Task Sequences rely on MDT 2010 and are highly customized with our own scripts which I integrated into MDT 2010.
Each application has its own collection and uses sub-collection/s + queries to ensure Install/Uninstall.
Collections are populated with queries what link to similarly named AD groups.
Our Service Desk allocation applications by targeting machines/users to these AD groups.
Software Updates are integrated with SCUP for Adobe and other custom updates.
FlexNet Management Platform is integrated with SCCM 2007 for inventory data.

Where we want to be.
To better support our upcoming VDI/Citrix implementation we need APPV5.
To get APPV5, we need Config Manager 2012 (and we might aswell implement R2)
We will upgrade all 150+ APPV4.6 Apps to APPV5.
We will migrate MSI/EXE/VBS using the built in migration tools.
We will drag across our Task Sequences kicking and screaming.
We will enhance our Master Task sequence with logic to better support Citrix/VDI
We will implement UE-V and do away with Roaming Profiles.
We will move to the App Model to support force install/uninstall.

How we will get there.

At the time of writing this blog I have already, in the space of 2 weeks:
Installed and configured Config Manager 2012
Installed all the roles (Software Updates, Reporting Services)
Migrated all MSI/EXE/VBS applications.
Powershell Script converted all (bar 6) APPV4.6 applications to APPV5
Powershell Script imported all APPV5 applications, created their collections, queries, deployments and distributed.
Installed ADK 8.1 + MDT 2013 and upgraded our Build Task Sequence.

The next coming posts will detail the powershell scripts.

FSRM Powershell followup the II

Since posting the original script, a number of use cases have popped up that are really simple to implement.

Use Case 1.

Our helpdesk wanted to do the following: (A quick and easy way to increase /decrease someone’s quota)
They simply wanted to specify a username to modify.
This could be done before by going:

Get-Quota | Where-Object {$_.UserName -eq “testuser”} | Set-Quota “5GB Limit”

The downside to this was that dirquota.exe in the background had to query every single quota, which takes ages.
This would be better:

Get-Quota -Username testuser | Set-Quota “5GB Limit”

To implement this all we need to do us match up the username parameter with a path.
dirquota.exe accepts a /path: parameter which always returns quickly no matter how many folders there are. This will work in our environment because the folders are named the same as the user’s SamAccountName AD attribute.
The difficulty comes when you have quota’ed folders on different volumes for different accounts.

ie: in our environment, Staff have a different volume than students.

So, on the file server, students are F:\studenthome, and staff are E:\staffhome
To get around this

        If ( $UserName -ne $null ) {
            If (!(Get-Module -Name ActiveDirectory)) {
                If (!(Get-Module -ListAvailable -Name ActiveDirectory)) { Write-Host -ForegroundColor Red "RSA Tools not available"; return } Else {
                    Import-Module -Name ActiveDirectory
                }
            }
            Try { 
                $User = Get-ADUser -Identity $UserName
            } Catch { 
                Write-Host -ForegroundColor Red "$Username not found"; return 
            }
            If ( $User.DistinguishedName -match "Staff" ) {
                $QuotaPath = "/path:j:\staffhome\$($User.SamAccountName)"
            } ElseIf ( $User.DistinguishedName -match "Student" ) {
                $QuotaPath = "/path:f:\studenthome\$($User.SamAccountName)"
            }
        }

So essentially we are using the ActiveDirectory module to check the OU a username resides in and setting the dirquote.exe /path parameter to the appropriate full local path for that user.

Use Case 2.

Stephen Goudy wrote in with what would be a slight variation to Use Case 1.

What Stephen’s query boils down to is query a folder, if there is no quota, set one, otherwise ignore.
We take the same code used in Use Case 1. and rename it to “Path”
Since we are going to be performing all actions based on path and not username, we can strip out all the AD stuff and make it simple:

        If ( ($Path -ne $null) ) {
            $QuotaPath = "/path:$path"
        }

So, to specifically do what Stephen wants:

Get-ChildItem -Directory -Path F:\ | ForEach-Object {
    If (Get-Quota -Path $_.Fullname) {
        Write-Host -ForeGroundColor Green "Path $($_.FullName) already has quota applied, skipping..."
    } Else {
        Set-Quota -TemplateName "5GB Limit" -Path $_.FullName
    }
}

Here is the updated edition of the script with Path support and a few other minor changes/improvements I’ve made over the months.

function Get-Quota {
Get-Quota -ListTemplates

    Name                                    Limit                                   Type
    ----                                    -----                                   ----
    100 MB Limit                            100.00 MB                               Hard
    2.5GB Auto Soft Limit                   2.50 GB                                 Soft
    5GB Limit                               5.00 GB                                 Hard
    100GB Limit                             100.00 GB                               Soft
    2.5GB Hard Limit                        2.50 GB                                 Hard
    10GB Limit                              10.00 GB                                Hard
    30GB Limit                              30.00 GB                                Hard

    .EXAMPLE    
    C:\PS>Get-Quota -TemplateName "10GB Limit"

    UserName                                                    Available
    --------                                                    ---------
    PThindall                                                   3.44 GB
    WSmit                                                       1.02 GB
    ATinkert                                                    3.22 GB
    SFooi                                                       4.90 GB
    Wlions                                                      2.99 GB

    .EXAMPLE
    C:\PS>Get-Quota -TemplateName "2.5GB Auto Soft Limit" | Select-Object *

    UserName    : jdazzle
    Path        : J:\staffhome\jdazzle
    SharePath   : \\winfile02\staffhome\jdazzle
    Template    : 2.5GB Hard Limit (Matches template)
    Status      : Enabled
    Used        : 1.28 GB
    PercentUsed : 51
    Available   : 1.22 GB
    Peak        : 2.42 GB

    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 30 March 2012        
    ChangeLog:
    1.0 - First Release
    1.1 - Changed to use the new [pscustomobject]
    1.2 - Changed to reduce lag and show data immediately
    1.3 - Added path support
 #>
    Param([string]$Path,[string[]]$TemplateName,$Server="winfile02",[Switch]$ListTemplates)
    If (! (Test-CurrentAdminRights) ) { Write-Host -ForegroundColor Red "Please run as Admin"; return }
    If ($ListTemplates) {
        $QuotaCMD = dirquota t l /remote:$($Server)
        $QuotaCMD | Select-String -Pattern "Template Name" -Context 0,6 | `
            %{$NewObj = "" | Select-Object Name,Limit,Type; 
                $NewObj.Name = $_.Line.Split(":")[1].TrimStart(" "); 
                $NewObj.Limit = $_.Context.PostContext[0].Split(":")[1].TrimStart(" ").Split("(")[0]; 
                $NewObj.Type = $_.Context.PostContext[0].Split("(")[1].Split(")")[0];
                $NewObj}
    } Else {

        $defaultProperties = @('UserName','Available')
        $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',[string[]]$defaultProperties)
        $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
        If ( $TemplateName -ne $null ) {$SourceTemplate = "/sourcetemplate:$($TemplateName)"}
        If ( $Path -ne $null ) { $QuotaPath = "/path:$path" }
        $ErrorActionPreference = "SilentlyContinue"
        dirquota.exe q l /remote:$($Server) $SourceTemplate $QuotaPath | Select-String -Pattern "Quota Path" -Context 0,12 |
            %{
                $Line = 0
                While ( ($_.Context.PostContext[$Line] -match "\\") ) { $Line++ }
                [pscustomobject] @{
                    'UserName' = $_.Line.Split("\") | Select-Object -Last 1;
                    'Path' = $_.Line.Split(":",2)[1].TrimStart(" ");
                    'SharePath' = $_.Context.PostContext[0].Split(":")[1].TrimStart(" ");
                    'Template' = $_.Context.PostContext[$Line].Split(":")[1].TrimStart(" ");
                    'Status' = $_.Context.PostContext[$Line+1].Split(":")[1].TrimStart(" ");
                    'Used' = $_.Context.PostContext[$Line+3].Split(":")[1].TrimStart(" ").Split("(")[0];
                    'PercentUsed' = [int]$_.Context.PostContext[$Line+3].Split("(")[1].Split("%")[0];
                    'Available' = $_.Context.PostContext[$Line+4].Split(":")[1].TrimStart(" ");
                    'Peak' = $_.Context.PostContext[$Line+5].Split(":")[1].TrimStart(" ").Split("(")[0];
                } | Add-Member MemberSet PSStandardMembers $PSStandardMembers -PassThru
            }
    }
            $ErrorActionPreference = "Continue"
}

function Set-Quota {
Set-Quota -Path "J:\Staffhome\jdazzle" -TemplateName "2.5GB Hard Limit"

    Quota path J:\Staffhome\jdazzle modified to 2.5GB Hard Limit successfully

    .EXAMPLE    
    C:\PS>Get-Quota -TemplateName "2.5GB Auto Soft Limit" | Where-Object { $_.PercentUsed -lt 200 } | Set-Quota -TemplateName "5GB Limit"

    Quota path J:\staffhome\mmcallis modified to 5GB Limit successfully
    Quota path J:\staffhome\SDauk modified to 5GB Limit successfully
    Quota path J:\staffhome\vschriev modified to 5GB Limit successfully
    Quota path J:\staffhome\NKing modified to 5GB Limit successfully
    Quota path J:\staffhome\lcameron modified to 5GB Limit successfully
    Quota path J:\staffhome\jwatson modified to 5GB Limit successfully
    Quota path J:\staffhome\tlucke modified to 5GB Limit successfully
    Quota path J:\staffhome\msiddiqu modified to 5GB Limit successfully

    .EXAMPLE
    C:\PS>Get-Contents Paths.txt | Set-Quota -TemplateName "100 MB Limit"

    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 30 March 2012        
    ChangeLog:
    1.0 - First Release
#>
    Param([Parameter(Mandatory = $true,
                     ValueFromPipeLine = $true,
                     ValueFromPipeLineByPropertyName = $false)]$Path,$TemplateName="2.5GB Hard Limit",$server="winfile02")
    BEGIN {
        If (! (Test-CurrentAdminRights) ) { Write-Host -ForegroundColor Red "Please run as Admin"; return }
    }

    PROCESS {

    function Set-QuotaWorker {
        Param($Path,$TemplateName)    
            $QuotaCMD = dirquota q m /remote:$($Server) /Path:$Path /sourcetemplate:$TemplateName
            If ( $QuotaCMD -match "successfully" ) { Write-Host "Quota path $Path modified to $TemplateName successfully" }
            Else { Write-Host $QuotaCMD }
        }

    #Verify Template
    $Templates = Get-Quota -ListTemplates
    If ( ! ($Templates | Where-Object { $_.Name -eq $TemplateName }) ) { 
        Write-Host "No such template exists"
        $Templates
        return
    }
    If ( $PSBoundParameters.ContainsKey('Path') ) {

        $Path | ForEach-Object {
            If ($_ | Get-Member -Name Path) {
                Set-QuotaWorker -Path $_.Path -TemplateName $TemplateName
            } Else {
                Set-QuotaWorker -Path $_ -TemplateName $TemplateName
            }
        } 
    } Else {

    Set-QuotaWorker -UserName $UserName -TemplateName $TemplateName
    }
    }
}

function Test-CurrentAdminRights {
    #Return $True if process has admin rights, otherwise $False
    $user = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $Role = [System.Security.Principal.WindowsBuiltinRole]::Administrator
    return (New-Object Security.Principal.WindowsPrincipal $User).IsInRole($Role)
 }

function Invoke-AsAdmin() {
    Param([System.String]$ArgumentString = "")
    $NewProcessInfo = new-object "Diagnostics.ProcessStartInfo"
    $NewProcessInfo.FileName = [System.Diagnostics.Process]::GetCurrentProcess().path
    $NewProcessInfo.Arguments = "-file " + $MyInvocation.MyCommand.Definition + " $ArgumentString"
    $NewProcessInfo.Verb = "runas"
    $NewProcess = [Diagnostics.Process]::Start($NewProcessInfo)
    $NewProcess.WaitForExit()
}

FSRM PowerShell Followup

Over the weekend I chatted to my Dad about my FSRM PowerShell script and it so happened that he needed something like this. His requirement was that he a DFSR where one replica had to be rebuilt.

I’m not too familiar with DFSR, but he said the Quota’s don’t automatically get copied across. This is how I suggested he could use my script to copy across the Quotas.

1. Ensure the replica FSRM already has the templates configured and that their names match the templates used on the source replica.

Get-Quota -Server SourceServer | ForEach-Object { Set-Quota -Server DestServer -Path $_.Path -TemplateName $_.Template.Split(“(“)[0] }

The reason we have to do some string editing (note calling Split method), is that dirquota.exe returns the templatename used and whether it matches the source template or not. In this situation, all quota’s conform to the templates, so nothing need be done bar ensuring the names match.

Let me know if there are other ways you think these functions could be used and I’ll post about them.

Mass manage FSRM quota with Powershell

For some reason in FSRM 2008 R2, Microsoft did not add any cmdlets to manage Quota’s
and templates.

For most organizations these tools are probably uncessecary as the GUI and
dirquota.exe provides the ways of creating Quota templates and auto templates.

But what happens when you need to mass modify qutoa’s? You will be spending alot of
time right clicking single folders and copying quota templates.

As with anything I do, I try to find ways to batch process or automate things. Where
I work we are migrating everyone’s home folder from a Novell file server to a Windows
based, FSRM quota managed file system.

We are migrating everyone as they migrate simultanioisly from Windows XP to Windows 7. The problem we have is we don’t have an automated method to migrate their quota limits.

What we are doing to get around this is we have 3 quota templates.
1. 2.5GB Soft Quota – Auto Apply
2. 2.5GB hard quota
3. 5GB hard quota

Now, as the migration of their home data occurs overnight and our migration process has no knowledge of disk usage we do not want a hard quota preventing the migration from being successfull, but we need SOME quota so that FSRM can do its job.

POWERSHELL FUNCTIONS
I’ve create two powershell functions that utilise the FSRM commandline tool dirquote.exe

Get-Quota
Description: Uses dirquota.exe and parses the output into a custom object
Parameters: Server (Remote FSRM Server), TemplateName (Return results on this template only), ListTemplates (Show a list of templates)
Returned object: A returned object might look like this:

PS> Get-Quota -Server sccm01 -TemplateName “Monitor 500 MB Share”

UserName    : sue_reg
Path        : E:\Homes\sue_reg
SharePath   : \\SCCM01\Homes\sue_reg
Template    : Monitor 500 MB Share (Matches template)
Status      : Enabled
Used        : 16.50 MB
PercentUsed : 3
Available   : 483.50 MB
Peak        : 16.50 MB

Set-Quota
Description: Uses dirquote.exe to apply a quota template to a path.
Parameters: Server (Remote FSRM Server), TemplateName (Template name to apply to path), Path (Folder path to apply template to)
Usage: Set-Quota’s path paramter can be accepted from the pipeline ie:

PS> Get-Quota | Set-Quota -TemplateName “250 MB Extended Limit”
Quota path E:\Homes\jessdazzlement modified to 250 MB Extended Limit successfully
Quota path E:\Homes\sue_it modified to 250 MB Extended Limit successfully
Quota path E:\Homes\sue_reg modified to 250 MB Extended Limit successfully

And finally, how I use these commands together:
PS> Get-Quota -TemplateName “2.5GB Soft Quota” | Where-Object {$_.PercentUsed -lt 100} | Set-Quota -TemplateName “2.5GB Hard Quota”

PS1 File (Written with some Powershell v3 CTP features used, I’ll post a v2 version later)

Update 2: New version of the script removes unnecessary delays in waiting for dirquota.exe parsing

Update: If you want this script to work on PowerShell v2, swap this line:

-[pscustomobject] @{
+New-Object -TypeName PSObject -Property @{
function Get-Quota {
<#
    .SYNOPSIS
    Retreive Quota information using dirqutoa with an FSRM Windows server.

    .DESCRIPTION
    Uses dirquota.exe and parses returned strings into a custom object usable in Powershell.

    .PARAMETER TemplateName
    The name of a quota template. Restricts query to return results from Quota's matching named template.

    .PARAMETER ListTemplates
    A switch which will simply output a list of templates configured on the FSRM server.

    .PARAMETER Server
    Accepts the name of the FSRM you are connecting to. Defaults to winfile02

    .EXAMPLE
    C:\PS>Get-Quota -ListTemplates

    Name                                    Limit                                   Type
    ----                                    -----                                   ----
    100 MB Limit                            100.00 MB                               Hard
    2.5GB Auto Soft Limit                   2.50 GB                                 Soft
    5GB Limit                               5.00 GB                                 Hard
    100GB Limit                             100.00 GB                               Soft
    2.5GB Hard Limit                        2.50 GB                                 Hard
    10GB Limit                              10.00 GB                                Hard
    30GB Limit                              30.00 GB                                Hard

    .EXAMPLE    
    C:\PS>Get-Quota -TemplateName "10GB Limit"

    UserName                                                    Available
    --------                                                    ---------
    PThindall                                                   3.44 GB
    WSmit                                                       1.02 GB
    ATinkert                                                    3.22 GB
    SFooi                                                       4.90 GB
    Wlions                                                      2.99 GB

    .EXAMPLE
    C:\PS>Get-Quota -TemplateName "2.5GB Auto Soft Limit" | Select-Object *

    UserName    : jdazzle
    Path        : J:\staffhome\jdazzle
    SharePath   : \\winfile02\staffhome\jdazzle
    Template    : 2.5GB Hard Limit (Matches template)
    Status      : Enabled
    Used        : 1.28 GB
    PercentUsed : 51
    Available   : 1.22 GB
    Peak        : 2.42 GB

    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 30 March 2012        
    ChangeLog:
    1.0 - First Release
    1.1 - Changed to use the new [pscustomobject]
    1.2 - Changed to reduce lag and show data immediately
 #>
    Param([string[]]$TemplateName,$Server="winfile02",[Switch]$ListTemplates)
    If (! (Test-CurrentAdminRights) ) { Write-Host -ForegroundColor Red "Please run as Admin"; return }
    If ($ListTemplates) {
        $QuotaCMD = dirquota t l /remote:$($Server)
        $QuotaCMD | Select-String -Pattern "Template Name" -Context 0,6 | `
            %{$NewObj = "" | Select-Object Name,Limit,Type; 
                $NewObj.Name = $_.Line.Split(":")[1].TrimStart(" "); 
                $NewObj.Limit = $_.Context.PostContext[0].Split(":")[1].TrimStart(" ").Split("(")[0]; 
                $NewObj.Type = $_.Context.PostContext[0].Split("(")[1].Split(")")[0];
                $NewObj}
    } Else {
       $defaultProperties = @('UserName','Available')
        $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',[string[]]$defaultProperties)
        $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
        If ( $TemplateName -ne $null ) {$SourceTemplate = "/sourcetemplate:$($TemplateName)"}
        $ErrorActionPreference = "SilentlyContinue"
        dirquota.exe q l /remote:$($Server) $SourceTemplate | Select-String -Pattern "Quota Path" -Context 0,12 |
            %{
                $Line = 0
                While ( ($_.Context.PostContext[$Line] -match "\\") ) { $Line++ }
                [pscustomobject] @{
                    'UserName' = $_.Line.Split("\") | Select-Object -Last 1;
                    'Path' = $_.Line.Split(":",2)[1].TrimStart(" ");
                    'SharePath' = $_.Context.PostContext[0].Split(":")[1].TrimStart(" ");
                    'Template' = $_.Context.PostContext[$Line].Split(":")[1].TrimStart(" ");
                    'Status' = $_.Context.PostContext[$Line+1].Split(":")[1].TrimStart(" ");
                    'Used' = $_.Context.PostContext[$Line+3].Split(":")[1].TrimStart(" ").Split("(")[0];
                    'PercentUsed' = [int]$_.Context.PostContext[$Line+3].Split("(")[1].Split("%")[0];
                    'Available' = $_.Context.PostContext[$Line+4].Split(":")[1].TrimStart(" ");
                    'Peak' = $_.Context.PostContext[$Line+5].Split(":")[1].TrimStart(" ").Split("(")[0];
                } | Add-Member MemberSet PSStandardMembers $PSStandardMembers -PassThru
            }
    }
            $ErrorActionPreference = "Continue"
}

function Set-Quota {
<#
    .SYNOPSIS
    Sets a quota template on a path using dirqutoa.exe with an FSRM Windows server.

    .DESCRIPTION
    Uses dirquota.exe and output from Get-Quota to modify a path quota to match that of a quota Template.

    .PARAMETER TemplateName
    The name of a quota template. Specifies the quota template to use on a path.

    .PARAMETER Path
    Specified the folder path to modify quota on.

    .PARAMETER Server
    Accepts the name of the FSRM you are connecting to. Defaults to winfile02

    .EXAMPLE
    C:\PS>Set-Quota -Path "J:\Staffhome\jdazzle" -TemplateName "2.5GB Hard Limit"

    Quota path J:\Staffhome\jdazzle modified to 2.5GB Hard Limit successfully

    .EXAMPLE    
    C:\PS>Get-Quota -TemplateName "2.5GB Auto Soft Limit" | Where-Object { $_.PercentUsed -lt 200 } | Set-Quota -TemplateName "5GB Limit"

    Quota path J:\staffhome\mmcallis modified to 5GB Limit successfully
    Quota path J:\staffhome\SDauk modified to 5GB Limit successfully
    Quota path J:\staffhome\vschriev modified to 5GB Limit successfully
    Quota path J:\staffhome\NKing modified to 5GB Limit successfully
    Quota path J:\staffhome\lcameron modified to 5GB Limit successfully
    Quota path J:\staffhome\jwatson modified to 5GB Limit successfully
    Quota path J:\staffhome\tlucke modified to 5GB Limit successfully
    Quota path J:\staffhome\msiddiqu modified to 5GB Limit successfully

    .EXAMPLE
    C:\PS>Get-Contents Paths.txt | Set-Quota -TemplateName "100 MB Limit"

    .NOTES
    Author: Jesse Harris
    For: University of Sunshine Coast
    Date Created: 30 March 2012        
    ChangeLog:
    1.0 - First Release
#>
    Param([Parameter(Mandatory = $true,
                     ValueFromPipeLine = $true,
                     ValueFromPipeLineByPropertyName = $false)]$Path,$TemplateName="2.5GB Hard Limit",$server="winfile02")
    BEGIN {
        If (! (Test-CurrentAdminRights) ) { Write-Host -ForegroundColor Red "Please run as Admin"; return }
    }

    PROCESS {

    function Set-QuotaWorker {
        Param($Path,$TemplateName)    
            $QuotaCMD = dirquota q m /remote:$($Server) /Path:$Path /sourcetemplate:$TemplateName
            If ( $QuotaCMD -match "successfully" ) { Write-Host "Quota path $Path modified to $TemplateName successfully" }
            Else { Write-Host $QuotaCMD }
        }

    #Verify Template
    $Templates = Get-Quota -ListTemplates
    If ( ! ($Templates | Where-Object { $_.Name -eq $TemplateName }) ) { 
        Write-Host "No such template exists"
        $Templates
        return
    }
    If ( $PSBoundParameters.ContainsKey('Path') ) {

        $Path | ForEach-Object {
            If ($_ | Get-Member -Name Path) {
                Set-QuotaWorker -Path $_.Path -TemplateName $TemplateName
            } Else {
                Set-QuotaWorker -Path $_ -TemplateName $TemplateName
            }
        } 
    } Else {

    Set-QuotaWorker -UserName $UserName -TemplateName $TemplateName
    }
    }
}

function Test-CurrentAdminRights {
    #Return $True if process has admin rights, otherwise $False
    $user = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $Role = [System.Security.Principal.WindowsBuiltinRole]::Administrator
    return (New-Object Security.Principal.WindowsPrincipal $User).IsInRole($Role)
 }

function Invoke-AsAdmin() {
    Param([System.String]$ArgumentString = "")
    $NewProcessInfo = new-object "Diagnostics.ProcessStartInfo"
    $NewProcessInfo.FileName = [System.Diagnostics.Process]::GetCurrentProcess().path
    $NewProcessInfo.Arguments = "-file " + $MyInvocation.MyCommand.Definition + " $ArgumentString"
    $NewProcessInfo.Verb = "runas"
    $NewProcess = [Diagnostics.Process]::Start($NewProcessInfo)
    $NewProcess.WaitForExit()
}

Deploying Outlook for Office 365 in a medium sized business Part 3

3. Create/Deploy Group Policy which sets Outlook as default on Go-Live

Most of the guts of the group policies discussed here can be determined from my previous blog post Deploying Outlook for Office 365 in a medium sized business Part 1

The guts of this post will highlight the easiest way to swap out the previous client Group Policy with the new Outlook group policy which will change the default. The key here, as always in IT is KISS.

1. Name your Group Policy’s so that they are obvious as to what they do.

Image

You can clearly tell which of my policy’s are for what. Prestage and PostStage. On Go-Live, PreStage will be swapped out for PostStage.

2. Disable unused components of the Group Policy’s

If a policy is machine based, disable the user portion of the policy. If the policy is a PostStage policy and will not be used until Go-Live disable the policy completely.

3. Link the Policy to all required levels prior to Go-Live

Image

There will be no harm of linking these policies to their appropriate locations ahead of time provided you have disabled them before linking them.

4. On Go-Live disable the Prestage Policies and enable the PostStage policies.

4. Other Group Policy Settings

When deploying Big Bang style with Outlook for Office 365, you will likely encounter some link saturation as everyone launches their client for the first time. Because of this it is advisable to disable Cached Exchange mode and then role it out slowly after Go-Live.

This setting can be found under the user Administrative Templates (So long as you have the Office 2010 ADMX files installed into your domain sysvol)\Microsoft Outlook 2010\Account Settings\Exchange\Do not allow an OST file to be created

Deploying Outlook for Office 365 in a medium sized business Part 2

2. Deploy Outlook 2010

If you have already deployed Office 2010 without Outlook then adding outlook to your deployment is fairly straight forward, although there are a couple of ‘gocha’s’ to look out for.

1. Don’t deploy Outlook as a standalone package. It is not supported by Microsoft if you have Office and Outlook installed as separate packages. If you do this several features of Outlook will be unavailable. See kb2491363 for more info.

2. Use the Office Customization tool to create an MSP customizations

Have a read of the MS article for more details, but essentially:

a) Make a copy of the MSP you used for office 2010

b) run setup.exe /Admin and browse to your new msp

c) Under Features–>Set feature installation state, select Outlook and choose ‘Run all from my computer’

d) Under Outlook–>Outlook profile specify your PRF

More information about the PRF

If your new to Outlook deployments like I am, you might ask ‘Dear Sir, What praytell is a PRF?’

If I had to guess, PRF is short for ‘Profile’. You see when you install Outlook it has no magical way of knowing your email settings, so it is up to each and every user to configure their email address, account username/password, server address, caching on, caching off.

This is not the best experience especially if you are about to deploy this Big Bang style to 1000+ users. Your help desk will hate you.

Enter PRF. You can use the Office Customization tool to generate a PRF that deploys settings like:

  • Add Email accounts (Exchange, Pop3, Imap
  • Add PST file
  • Add LDAP directory
  • Add Outlook Address book
  • Configure Exchange caching and offline behaviour.

When deploying Outlook for Office 365 you cannot simply configure your PRF with an exchange account pointing to your Office 365 server, because there is no single server when dealing with Office 365. You must use a protocol called ‘Auto discovery’ to detect your exchange server for each individual account. Autodiscovery works by the outlook client query’s DNS for autodiscover.yourcompany.com autodiscover.yourcompany.com must be a cname DNS record that points to autodiscover.outlook.com. It also uses your primary UPN name in AD to detect the email address. (See more here)

The short of it is, if you want to configure a PRF for autodiscovery, you don’t add an account you leave it blank:

;Automatically generated PRF file from the Microsoft Office Customization and Installation Wizard

; **************************************************************
; Section 1 - Profile Defaults
; **************************************************************

[General]
Custom=1
ProfileName=OUTLOOK-PROFILE
DefaultProfile=Yes
OverwriteProfile=Yes
ModifyDefaultProfileIfPresent=false

; **************************************************************
; Section 2 - Services in Profile
; **************************************************************

[Service List]
;ServiceX=Microsoft Outlook Client

;***************************************************************
; Section 3 - List of internet accounts
;***************************************************************

[Internet Account List]

;***************************************************************
; Section 4 - Default values for each service.
;***************************************************************

;[ServiceX]
;FormDirectoryPage=
;-- The URL of Exchange Web Services Form Directory page used to create Web forms.
;WebServicesLocation=
;-- The URL of Exchange Web Services page used to display unknown forms.
;ComposeWithWebServices=
;-- Set to true to use Exchange Web Services to compose forms.
;PromptWhenUsingWebServices=
;-- Set to true to use Exchange Web Services to display unknown forms.
;OpenWithWebServices=
;-- Set to true to prompt user before opening unknown forms when using Exchange Web Services.

;***************************************************************
; Section 5 - Values for each internet account.
;***************************************************************

;***************************************************************
; Section 6 - Mapping for profile properties
;***************************************************************

[Microsoft Exchange Server]
ServiceName=MSEMS
MDBGUID=5494A1C0297F101BA58708002B2A2517
MailboxName=PT_STRING8,0x6607
HomeServer=PT_STRING8,0x6608
OfflineAddressBookPath=PT_STRING8,0x660E
OfflineFolderPathAndFilename=PT_STRING8,0x6610

[Exchange Global Section]
SectionGUID=13dbb0c8aa05101a9bb000aa002fc45a
MailboxName=PT_STRING8,0x6607
HomeServer=PT_STRING8,0x6608
ConfigFlags=PT_LONG,0x6601
RPCoverHTTPflags=PT_LONG,0x6623
RPCProxyServer=PT_UNICODE,0x6622
RPCProxyPrincipalName=PT_UNICODE,0x6625
RPCProxyAuthScheme=PT_LONG,0x6627
AccountName=PT_UNICODE,0x6620

[Microsoft Mail]
ServiceName=MSFS
ServerPath=PT_STRING8,0x6600
Mailbox=PT_STRING8,0x6601
Password=PT_STRING8,0x67f0
RememberPassword=PT_BOOLEAN,0x6606
ConnectionType=PT_LONG,0x6603
UseSessionLog=PT_BOOLEAN,0x6604
SessionLogPath=PT_STRING8,0x6605
EnableUpload=PT_BOOLEAN,0x6620
EnableDownload=PT_BOOLEAN,0x6621
UploadMask=PT_LONG,0x6622
NetBiosNotification=PT_BOOLEAN,0x6623
NewMailPollInterval=PT_STRING8,0x6624
DisplayGalOnly=PT_BOOLEAN,0x6625
UseHeadersOnLAN=PT_BOOLEAN,0x6630
UseLocalAdressBookOnLAN=PT_BOOLEAN,0x6631
UseExternalToHelpDeliverOnLAN=PT_BOOLEAN,0x6632
UseHeadersOnRAS=PT_BOOLEAN,0x6640
UseLocalAdressBookOnRAS=PT_BOOLEAN,0x6641
UseExternalToHelpDeliverOnRAS=PT_BOOLEAN,0x6639
ConnectOnStartup=PT_BOOLEAN,0x6642
DisconnectAfterRetrieveHeaders=PT_BOOLEAN,0x6643
DisconnectAfterRetrieveMail=PT_BOOLEAN,0x6644
DisconnectOnExit=PT_BOOLEAN,0x6645
DefaultDialupConnectionName=PT_STRING8,0x6646
DialupRetryCount=PT_STRING8,0x6648
DialupRetryDelay=PT_STRING8,0x6649

[Personal Folders]
ServiceName=MSPST MS
Name=PT_STRING8,0x3001
PathAndFilenameToPersonalFolders=PT_STRING8,0x6700
RememberPassword=PT_BOOLEAN,0x6701
EncryptionType=PT_LONG,0x6702
Password=PT_STRING8,0x6703

[Unicode Personal Folders]
ServiceName=MSUPST MS
Name=PT_UNICODE,0x3001
PathAndFilenameToPersonalFolders=PT_STRING8,0x6700
RememberPassword=PT_BOOLEAN,0x6701
EncryptionType=PT_LONG,0x6702
Password=PT_STRING8,0x6703

[Outlook Address Book]
ServiceName=CONTAB

[LDAP Directory]
ServiceName=EMABLT
ServerName=PT_STRING8,0x6600
UserName=PT_STRING8,0x6602
UseSSL=PT_BOOLEAN,0x6613
UseSPA=PT_BOOLEAN,0x6615
EnableBrowsing=PT_BOOLEAN,0x6622
DisplayName=PT_STRING8,0x3001
ConnectionPort=PT_STRING8,0x6601
SearchTimeout=PT_STRING8,0x6607
MaxEntriesReturned=PT_STRING8,0x6608
SearchBase=PT_STRING8,0x6603
CheckNames=PT_STRING8,0x6624
DefaultSearch=PT_LONG,0x6623

[Microsoft Outlook Client]
SectionGUID=0a0d020000000000c000000000000046
FormDirectoryPage=PT_STRING8,0x0270
WebServicesLocation=PT_STRING8,0x0271
ComposeWithWebServices=PT_BOOLEAN,0x0272
PromptWhenUsingWebServices=PT_BOOLEAN,0x0273
OpenWithWebServices=PT_BOOLEAN,0x0274
CachedExchangeMode=PT_LONG,0x041f
CachedExchangeSlowDetect=PT_BOOLEAN,0x0420

[Personal Address Book]
ServiceName=MSPST AB
NameOfPAB=PT_STRING8,0x001e3001
PathAndFilename=PT_STRING8,0x001e6600
ShowNamesBy=PT_LONG,0x00036601

; ************************************************************************
; Section 7 - Mapping for internet account properties.  DO NOT MODIFY.
; ************************************************************************

[I_Mail]
AccountType=POP3
;--- POP3 Account Settings ---
AccountName=PT_UNICODE,0x0002
DisplayName=PT_UNICODE,0x000B
EmailAddress=PT_UNICODE,0x000C
;--- POP3 Account Settings ---
POP3Server=PT_UNICODE,0x0100
POP3UserName=PT_UNICODE,0x0101
POP3UseSPA=PT_LONG,0x0108
Organization=PT_UNICODE,0x0107
ReplyEmailAddress=PT_UNICODE,0x0103
POP3Port=PT_LONG,0x0104
POP3UseSSL=PT_LONG,0x0105
; --- SMTP Account Settings ---
SMTPServer=PT_UNICODE,0x0200
SMTPUseAuth=PT_LONG,0x0203
SMTPAuthMethod=PT_LONG,0x0208
SMTPUserName=PT_UNICODE,0x0204
SMTPUseSPA=PT_LONG,0x0207
ConnectionType=PT_LONG,0x000F
ConnectionOID=PT_UNICODE,0x0010
SMTPPort=PT_LONG,0x0201
SMTPSecureConnection=PT_LONG,0x020A
ServerTimeOut=PT_LONG,0x0209
LeaveOnServer=PT_LONG,0x1000

[IMAP_I_Mail]
AccountType=IMAP
;--- IMAP Account Settings ---
AccountName=PT_UNICODE,0x0002
DisplayName=PT_UNICODE,0x000B
EmailAddress=PT_UNICODE,0x000C
;--- IMAP Account Settings ---
IMAPServer=PT_UNICODE,0x0100
IMAPUserName=PT_UNICODE,0x0101
IMAPUseSPA=PT_LONG,0x0108
Organization=PT_UNICODE,0x0107
ReplyEmailAddress=PT_UNICODE,0x0103
IMAPPort=PT_LONG,0x0104
IMAPUseSSL=PT_LONG,0x0105
; --- SMTP Account Settings ---
SMTPServer=PT_UNICODE,0x0200
SMTPUseAuth=PT_LONG,0x0203
SMTPAuthMethod=PT_LONG,0x0208
SMTPUserName=PT_UNICODE,0x0204
SMTPUseSPA=PT_LONG,0x0207
ConnectionType=PT_LONG,0x000F
ConnectionOID=PT_UNICODE,0x0010
SMTPPort=PT_LONG,0x0201
SMTPSecureConnection=PT_LONG,0x020A
ServerTimeOut=PT_LONG,0x0209
CheckNewImap=PT_LONG,0x1100
RootFolder=PT_UNICODE,0x1101
Account=PT_UNICODE,0x0002
HttpServer=PT_UNICODE,0x0100
UserName=PT_UNICODE,0x0101
Organization=PT_UNICODE,0x0107
UseSPA=PT_LONG,0x0108
TimeOut=PT_LONG,0x0209
Reply=PT_UNICODE,0x0103
EmailAddress=PT_UNICODE,0x000C
FullName=PT_UNICODE,0x000B
Connection Type=PT_LONG,0x000F
ConnectOID=PT_UNICODE,0x0010

3. Ensure your command line creates an msi log

Always run your MSI installations creating verbose log files. ie: msiexec.exe /p Office-Custom-Outlook.msp /l* C:\Windows\AppLog\OutlookMSP.log

4. I always recommend using a custom script to launch your setup command lines as they can be tailored to generate better logging for troubleshooting purposes. I’ll be creating a blog post in the future with my custom script.

Continue reading Deploying Outlook for Office 365 in a medium sized business Part 3

Deploying Outlook for Office 365 in a medium sized business Part 1

Briefing

When deploying Outlook 2010 for a medium sized organization (1500 machines) I encountered a number of stumbling blocks I had to solve which I will outline here in the hopes that it may help some others in the internets.

The information here will specifically help people who:

1. Have an mail client default other than Outlook

2. Require outlook to be configured differently in different location.

3. Use GroupWise as the current default client (Can be swapped for any other client)

Deployment plan.

In this organization it was decided that the crossover from GroupWise to Outlook/Office 365 would be done in the Big Bang approach. This necessitated that in order to make the transition quickly and smoothly, outlook would be deployed ahead of go-live and retroactively made the default email client.

1. Create/Deploy Group Policy settings that enforce GroupWise as the default client.

This is done because when most clients are installed, part of their installation process sets them as the default client.

2. Deploy Outlook 2010

If Office is already installed, use an MSP to reconfigure office to include Outlook. When I first began deployment testing I made the mistake of deploying Outlook as a separate application. This appeared to work well but further investigation lead to features missing until I stumbled on this article: kb2491363

3. Create/Deploy Group Policy which sets Outlook as default on Go-Live

Details

1. Group Policy

for Windows XP

On Windows XP, the default mail client is set by Machine registry settings.
The simplest way to deploy Registry settings is with Group Policy Preferences. If you haven’t already done so, now is the time to reach out for KB943729 and deploy it.

There are two places in HKLM that determine your default email client :

A) HKLM\Software\Clients\Mail
This Key’s default name data specifies a subkey with settings deployed by the client.
eg:
HKLM\Software\Clients\Mail    (default)    GroupWise
HKLM\Software\Clients\Mail\GroupWise

Typically the only thing you will need to deploy here is the default value of the Mail key, as the installation of the email client will take care of all the information in the subkeys. In this case I deployed:

HKLM\Software\Clients\Mail    (default)    GroupWise

B) HKLM\Software\Classes\mailto
This setting defines which client (and how) is launched when a mailto link is clicked in any installed Web browser.
The contents of this key/subkeys is a direct copy of the data beneath HKLM\Software\Clients\Mail\GroupWise (or other client). It is automatically set when the default email client is set from Internet Explorer–>Internet Settings–>Programs tab.
Since we are not using the interface to set the default client, we must deploy these registry settings. Again the go-to tool should be Group Policy Preferences.

for Windows 7

On Windows 7 the default client is set by machine settings and customized by user settings. This means that when a new user profile is created on Windows 7, the account copies information from the machine settings into the user profile. You must change the settings for machine for potential new users and change the settings users for current users.

Machine Settings

The same HKLM settings that apply for Windows XP also apply to Windows 7.Be warned though if you deploy x86 and x64 architectures you will likely need to deploy different settings for each architecture. This is because many of the subkeys and values will refer to the email client executable and they will not be the same path between archtecutes. eg, On x86 GroupWise is installed to C:\Program Files\Novell\GroupWise. Where on x64, it is installed to C:\Program Files (x86)\Novell\GroupWise.

To work around this, I usually apply duplicate settings for each instance and use Item Level Targeting to apply the correct one.

User Settings

There are three main areas in HKCU that need to be deployed.

A)HKCU\Software\Clients\Mail
This key needs to specify the subkey of HKLM\Software\Clients\Mail pertaining to the desired default mail client. No other subkeys of this key is nessecary.

B)HKCU\Software\Microsoft\Windows\Shell\Associations\UrlAssociations
*\MAILTO\UserChoice
*\mapi\UserChoice
*\webcal\UserChoice

These keys all contain a value ‘Progid’ and valuedata specific to the application.
GroupWise has
‘GroupWise.Url.mailto’ for MAILTO and mapi
‘GroupWise.Url.webcal’ for webcal
You can determine what another client would set, by adjusting the default client using the interface in Windows 7 and monitorying these registry keys.
The interface can be found at Control Panel–>Programs–>Default Programs–>Set your default programs

C)File types

Most email clients also set a couple of file types. GroupWise sets .ics, .vcf and .vcs (It actually sets more, but these are the ones that overlap with Outlook)

HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts
*\.ics\UserChoice
*\.vcf\UserChoice
*\.vcs\UserChoice

These keys all contain a value ‘Progid’ and value data specific to the application.
GroupWise has
‘GroupWise.File.ics’ for .vcs and .ics
‘GroupWise.File.vcd’ for .vcf

Continue reading Deploying Outlook for Office 365 in a medium sized business Part 2