WVD - Assigning WVD Users - Support Staff / Help Desk Script

Recently I've been deploying a number of WVD platforms and one of the tricky bit is making the WVD assignment 'support staff friendly'.

I've previously posted a blog entry documenting the required PowerShell commands to help out with setting up WVD users, but still this was manual and needed work in order for it to be used in a production environment. I also had some concerns as it exposed the Azure Storage Account Access Key.

With the thanks to @James_Tighe here is a script that will read a saved secret created within an Azure Key Vault that contains storage account Access Key.

The below script will create a Function called AssignWVDUser that you can use with the following syntax to either enable an individual user via a UPN or a batch of users from a CSV:


AssignWVDUser -upn "john.smith@domain.com"
AssignWVDUser -CSVPath "C:\temp\list.csv"

This will save a log file to the %TEMP%\WVDLog.log but this can be amended if required.

On overview of function is:

  • Import PowerShell Modules
  • Connect to Azure
  • Set the correct Azure Subscription
  • Connect to the WVD tenant
  • Obtain the Storage Key from the Key Vault
  • Assign users to the WVD Host Pool
  • Set the FSLogix Profile store as the storage scope
  • Set the IAM Roles to the Azure Storage for FSLogix
  • Map a Z: drive to the FSLogix Storage
  • Assign the correct NTFS permissions to the FSLogix Profile share
  • Disconnect the Z: drive
To set this up:
  1. First of all, I've created a service account within AD to use with the script, this saves support staff from having to enter credentials (and means the script could also be published as a Remote App if required).
  2. I then use New-StoredCredential.ps1 from https://blogs.technet.microsoft.com/exoshellwizard/2016/05/12/scheduling-a-task-against-exchange-online/ to save the credentials of a service account in a text file in the directory I'm saving my script.
  3. Now you need to setup an Azure Key Vault and save the Storage Account access key as a Secret  (Take a look at https://docs.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage#view-access-keys-and-connection-string to see how to access the Access Keys)
  4. Give the service account permission to the Key Vault in the way of an Access Policy - https://docs.microsoft.com/en-us/azure/key-vault/key-vault-secure-your-key-vault
  5. Give the Service Account permission to your storage account and the Azure File Share hosting your FSLogix profiles
  6. Assign the service account the RDS Owner Role - https://docs.microsoft.com/en-us/powershell/module/windowsvirtualdesktop/new-rdsroleassignment
  7. Run the script by running the following commands (you could store this in your PowerShell profile if you wished):
Set-ExecutionPolicy Unrestricted -Scope Process
Import-module C:\InstallPoint\WVD-AssignWVDUser\AssignWVDUser.ps1 -Force
AssignWVDUser -upn "FIRSTNAME.LASTNAME@DOMAIN.COM" 

The script will run through and if you have everything configured correctly  you'll see the following output and your users should now be able to login to the WVD using the account detials you have configured:




I've saved a copy of the script here but also highlighted below areas you need to amend:


Function AssignWVDUser {
    <#
       .SYNOPSIS
       Creates a Window Virtual Desktop users for the WVD Host Pool.
       .EXAMPLE
       AssignWVDUser -upn "john.smith@domain.com"
       .EXAMPLE
       AssignWVDUser -CSVPath "C:\temp\list.csv"
       .DESCRIPTION
       This commands add the relevant user to the WVD platform.
     #>

    param (
        [Parameter(Mandatory=$false)]
        [string] $upn,
        [Parameter(Mandatory=$false)]
        [string] $CSVPath,
        [Parameter(Mandatory=$false)]
        [String] $HostPoolName = "YourHostPool",
        [Parameter(Mandatory=$false)]
        [String] $AppGroupName = "Desktop Application Group"
    )

    #Variables

    $tenantName = "youWVDtenant"
    $brokerurl = "https://rdbroker.wvd.microsoft.com"
    $aadTenantId = "yourAADteantID"
    $azureSubscriptionId = "YouSubscriptionID"
    $vaultName = "YourKeyVault"
    $secretName = "YourAccessKey"
    $PasswordPath = "C:\yoursavepassword.txt"
    $SecurePassword = Get-Content $PasswordPath | ConvertTo-SecureString
    $UserCreds = New-Object System.Management.Automation.PSCredential -ArgumentList Username@domain.com, $SecurePassword
       
 
    #Logging
    function Write-Log
    {
        param
        (
            [Parameter(Mandatory = $false)]
            [string]$Message,
            [Parameter(Mandatory = $false)]
            [string]$Error,
            [Parameter(Mandatory = $false)]
            [bool]$Tee = $false
        )
   
        try {
            $Date = Get-Date -format "dd-MM-yy HH:mm:ss"
            $invocation = "$($MyInvocation.MyCommand.Source):$($MyInvocation.ScriptLineNumber)"
            if ($Message)
            {
                Add-Content -Value "$Date - $invocation - $Message" -Path "$([environment]::GetEnvironmentVariable('TEMP', 'Machine'))\WVDLog.log"
                if ($Tee) {
                    Write-Host -ForegroundColor Green $Message
                }
            }
            else {
                Add-Content -Value "$Date - $invocation - $Error" -Path "$([environment]::GetEnvironmentVariable('TEMP', 'Machine'))\WVDLog.log"
                if ($Tee) {
                    Write-Host -ForegroundColor Red $Error
                }
            }
        }
        catch {
            Write-Error $_.Exception.Message
        }
    }

    #Import PowerShell Modules
    try {
        Write-Log -Message "Importing RDInfra PowerShell Module" -Tee:$true
        Import-Module -Name Microsoft.RDInfra.RDPowershell -ErrorAction stop
        Write-Log -Message "Module Imported Successfuly"
    } catch {
        Write-Log -Error "Error importing RDInfr PowerShell Module" -Tee:$true
        Write-Log -Error $_.Exception.Message -Tee:$true
    }

    #Connect to Azure
   
    try {
        Write-Log -Message "Connecting to Azure as user: $($usercreds.username)" -Tee:$true
        Connect-AzAccount -Credential $userCreds -ErrorAction Stop
    } catch {
        Write-Log -Error "Error connecting to Azure" -Tee:$true
        Write-Log -Error $_.Exception.Message -Tee:$true
        return
    }

    #Set the correct Azure Subscription
    Write-Log -Message "Setting Azure Context to customer subscription" -Tee:$true
    Set-AzContext -SubscriptionId $azureSubscriptionId

    #Connect to the WVD tenant
    try {
        Write-Log -Message "Authenticating to WVD Tenant: $tenantName" -Tee:$true
        Add-RdsAccount -DeploymentUrl $brokerurl -Credential $UserCreds -ErrorAction Stop
        Write-Log -Message "Successfully Authenticated to WVD Tenant: $tenantName" -Tee:$true
    } catch {
        Write-Log -Error "Error authenticating to WVD Tenant: $tenantName" -Tee:$true
        Write-Log -Error $_.Exception.Message -Tee:$true
        return
    }
   
    #Set the IAM Roles
    $FileShareContributorRole = Get-AzRoleDefinition "Storage File Data SMB Share Contributor"
   
    #Obtain the Storage Key from the Key Vault
    Write-Log -Message "Getting Storage Account Key" -Tee:$true
    $secret = Get-AzKeyVaultSecret -VaultName $vaultName -Name $secretName
    $secret = $secret.SecretValueText

    #CSV File Process
    if ($CSVPath) {
   
        #Map the Storage
        Write-Log -Message "Mapping Storage Account File Share to Z:" -Tee:$true
        $cmd = net use z: \\yourstorageaccount.file.core.windows.net\yourshare $secret /user:Azure\storageaccountname
        if ($cmd -match "The command completed successfully.")
        {
            Write-Log -Message "Azure Storage Successfully mapped" -Tee:$true
        } else {
            Write-Log -Error "Error mapping Azure Storage. Exiting . . ." -Tee:$true
            Write-Log -Error $cmd -Tee:$true
            return
        }
        Write-Log -Message "Adding multiple users from CSV file" -Tee:$true
        try {
            Write-Log -Message "Importing User List from: $CSVPath" -Tee:$true
            $csv = Import-CSV -Path $CSVPath -ErrorAction Stop
            Write-Log -Message "User List Imported" -Tee:$true
        } catch {
            Write-Log -Error "Error Importing User List" -Tee:$true
            Write-Log -Error $_.Exception.Message -Tee:$true
        }
       
        #Assign users to the WVD Host Pool
        foreach ($user in $csv)
        {

            try {
                Write-Log -Message "Adding user: $($user.upn) to WVD" -Tee:$true
                Add-RdsAppGroupUser -TenantName $tenantName -HostPoolName $HostPoolName -AppGroupName $AppGroupName -UserPrincipalName $user.upn -ErrorAction Stop
                Write-Log -Message "Successfully added user" -Tee:$true
            } catch {
                Write-Log -Error "Error adding user" -Tee:$true
                Write-Log -Error $_.Exception.Message -Tee:$true
            }
           
            #Set the FSLogix Profile store as the storage scope
            $scope = "/subscriptions/yourAzureSubscription/resourceGroups/yourresourcegroup/providers/Microsoft.Storage/storageAccounts/yourstorageaccount/fileServices/default/fileshares/yourshare"
           
            #Assign users the required IAM roles
            try {
                Write-Log -Message "Adding role definitions to user $($user.upn)" -Tee:$true
                New-AzRoleAssignment -SignInName $($user.upn) -RoleDefinitionName $FileShareContributorRole.Name -Scope $scope -ErrorAction Stop
                Write-Log -Message "Successfully added role definitions to user $($user.upn)" -Tee:$true
           } catch {
               Write-Log -Error "Error adding role definitions to user $($user.upn)" -Tee:$true
               Write-Log -Error $_.Exception.Message -Tee:$true
           }
           #Assign the corect NTFS permissions to the FSLogix Profile share
           $grant = $user.upn + ":(f)"
           $result = icacls z: /grant $grant
           if ($result -match "Successfully processed 1 files") {
               Write-Log -Message "NTFS permissions assigned for user: $($user.upn)" -Tee:$true
           } else {
               Write-Log -Error "Failed to assign NTFS permissions for user: $($user.upn)" -Tee:$true
               Write-Log -Error $result -Tee:$true
           }
        }

        #Disconnect the Storage
        Write-Log -Message "Removing mapping for Azure Storage" -Tee:$true
        $result = net use z: /delete
        if ($result -match "z: was deleted successfully") {
            Write-Log -Message "Drive mapping removed" -Tee:$true
        } else {
            Write-Log -Error "Error removing drive map" -Tee:$true
            Write-Log -Error $result -Tee:$true
            return
        }

    #UPN Process
    } else {
        if ($upn)
        {
            #Map the Storage
            Write-Log -Message "Mapping Storage Account File Share to Z:" -Tee:$true
            $cmd = net use z: \\yourstorageaccount.file.core.windows.net\yourshare $secret /user:Azure\storageaccountname
            if ($cmd -match "The command completed successfully.")
            {
                Write-Log -Message "Azure Storage Successfully mapped" -Tee:$true
            } else {
                Write-Log -Error "Error mapping Azure Storage. Exiting . . ." -Tee:$true
                Write-Log -Error $cmd -Tee:$true
                return
            }
                 
            #Assign user to the WVD Host Pool
            Write-Log -Message "Adding single user: $upn" -Tee:$true
            try {
                Write-Log -Message "Adding user: $($upn) to WVD" -Tee:$true
                Add-RdsAppGroupUser -TenantName $tenantName -HostPoolName $HostPoolName -AppGroupName $AppGroupName -UserPrincipalName $upn -ErrorAction Stop
                Write-Log -Message "Successfully added user" -Tee:$true
            } catch {
                Write-Log -Error "Error adding user" -Tee:$true
                Write-Log -Error $_.Exception.Message -Tee:$true
            }
           
            #Set the FSLogix Profile store as the storage scope
            $scope = "/subscriptions/yourAzureSubscription/resourceGroups/yourresourcegroup/providers/Microsoft.Storage/storageAccounts/yourstorageaccount/fileServices/default/fileshares/yourshare"
           
            #Assign user the required IAM roles
            try {
                Write-Log -Message "Adding role definitions to user $upn" -Tee:$true
                New-AzRoleAssignment -SignInName $upn -RoleDefinitionName $FileShareContributorRole.Name -Scope $scope -ErrorAction Stop
                Write-Log -Message "Successfully added role definitions to user $upn" -Tee:$true
            } catch {
            Write-Log -Error "Error adding role definitions to user $upn" -Tee:$true
            Write-Log -Error $_.Exception.Message -Tee:$true
            }
            
            #Assign the corect NTFS permissions to the FSLogix Profile share
            $grant = $upn + ":(f)"
            $result = icacls z: /grant $grant

            if ($result -match "Successfully processed 1 files") {
                Write-Log -Message "NTFS permissions assigned for user: $upn" -Tee:$true
            } else {
                Write-Log -Error "Failed to assign NTFS permissions for user: $upn" -Tee:$true
                Write-Log -Error $result -Tee:$true
            }

            #Disconnect the Storage
            Write-Log -Message "Removing mapping for Azure Storage" -Tee:$true
            $result = net use z: /delete
            if ($result -match "z: was deleted successfully") {
                Write-Log -Message "Drive mapping removed" -Tee:$true
            } else {
                Write-Log -Error "Error removing drive map" -Tee:$true
                Write-Log -Error $result -Tee:$true
                return
            }
        } else {
            Write-Log -Message "No User Entered. Exiting. . . " -Tee:$true
            return
        }
    }
}


As I said before, big thanks to @James_Tighe for the help with the logging and Azure Key Vault aspect of this one!







Comments