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:
- 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).
- 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.
- 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)
- 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
- Give the Service Account permission to your storage account and the Azure File Share hosting your FSLogix profiles
- Assign the service account the RDS Owner Role - https://docs.microsoft.com/en-us/powershell/module/windowsvirtualdesktop/new-rdsroleassignment
- 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
Post a Comment