User Profile Disk 在網路異常或斷線等意外情況下
將導致 Collection Member 與 User Profile Server 失去聯繫
結果便是 User 的註冊機碼等無法在 Sign-Out 時回存
下次登入時就會變成 Temp Profile
且因為是 Pooled VM, 根本無法經由 Local Machine Registry 修復
解決辦法之邏輯如下:
A. User Sign-Out 時 (VBS, 在 Pooled VM 上進行)
User Sign-Out 時檢查 User Profile Disk Link 是否仍然有效
在 User Profile Server 的某一共用資料夾建立 Backup Queue File, 並載明 UserName / Domain / Collection Name 等資訊
B. User Sign-Out 後 (PowerShell, 在 User Profile Server 上進行)
根據上述 Queue File 找出 User Profile Disk 並進行掛載
將 User Profile 的 ntuser* 檔案備份到 AppData\Roaming\BackupNTUserData 路徑
解除掛載
C. User Sign-In 時 (VBS, 在 Pooled VM 上進行)
檢查 User Profile 是否正常, 正常就結束, 如果變為 Temp Profile 就要進行 Restore 作業
先利用 DiskPart 將 User Profile Disk 的 Partition 指派一磁碟機代號
再將 AppData\Roaming\BackupNTUserData 複製到根
移除磁碟機代號並要求 Sign-Out 重新登入
以下為程式碼, 包含 VBS 與 PowerShell
VBS 可用 GPO 派送, PowerShell 以工作排程每五分鐘檢查一次
UserProfileCheckAtLogoff.vbs
===== 程式開始 =====
on error resume next
RemotePath = "\\FileServer.Contoso.com\NTUserDataBackupQueue$"
AdminEmail = "UserName@Contoso.com"
Set objShell = CreateObject("WScript.Shell")
Set wshNetwork = CreateObject("WScript.Network")
ComputerNameArray = Split(wshNetwork.ComputerName,"-")
CollectionName = ComputerNameArray(0)
UserName = wshNetwork.UserName
UserProfilePath = "C:\Users\" & wshNetwork.UserName
EnvUserProfilePath = objShell.ExpandEnvironmentStrings("%UserProfile%")
Set fso = CreateObject("Scripting.FileSystemObject")
ProfileBackupFailMessage = ""
ProfileBackupMessage = ""
if (LCase(UserProfilePath) = LCase(EnvUserProfilePath)) then
' Test if backup folder not exist then create
BackupNTUserDataPath = UserProfilePath & "\AppData\Roaming\BackupNTUserData"
if not (fso.FolderExists(BackupNTUserDataPath)) then
Set CreateBackupNTUserDataFolder = fso.CreateFolder(BackupNTUserDataPath)
if (CreateBackupNTUserDataFolder <> BackupNTUserDataPath) then
ProfileBackupFailMessage = "Fail to Create Backup Folder, Please Check."
end if
end if
if (ProfileBackupFailMessage = "") then
' Test if user can create a test folder in backup folder
TestBackupNTUserDataPath = BackupNTUserDataPath & "\Test"
if (fso.FolderExists(TestBackupNTUserDataPath)) then
fso.DeleteFolder TestBackupNTUserDataPath, True
end if
Set CreateTestFolder = fso.CreateFolder(TestBackupNTUserDataPath)
if (CreateTestFolder <> TestBackupNTUserDataPath) then
ProfileBackupFailMessage = "Fail to Create Test Folder - Profile Folder not Accessable, Please Check."
end if
fso.DeleteFolder TestBackupNTUserDataPath, True
if (ProfileBackupFailMessage = "") then
' create backup queue
if (fso.FolderExists(RemotePath)) then
QueueFileName = RemotePath & "\" & UserName & "@" & wshNetwork.UserDomain & "@" & CollectionName
ProfileBackupMessage = "User Profile Backup Queue Create Successed."
WriteQueue = fso.OpenTextFile(QueueFileName, 2, true, -1)
WriteQueue.Close
end if
end if
end if
end if
Receiver = AdminEmail
'Receiver = Receiver & ";" & wshNetwork.UserName & "@Contoso.com"
MailSender = "Contoso MailMan"
Set objMessage = CreateObject("CDO.Message")
objMessage.Subject = "Contoso Sign-Out Notifier: " & UserName
objMessage.From = MailSender & " <MailMan@Contoso.com>"
objMessage.To = Receiver
Set wshNetwork = CreateObject("WScript.Network")
MessageBody = ""
MessageBody = MessageBody & "Your account " & UserName & " was Sign-Out from Workstation: " & vbcrlf & vbcrlf
MessageBody = MessageBody & "Contoso\" & wshNetwork.ComputerName & " @ " & Now & vbcrlf & vbcrlf
if (ProfileBackupFailMessage <> "") then
MessageBody = MessageBody & "User Profile Disk Might Offline After You Sign-In." & vbcrlf
MessageBody = MessageBody & ProfileBackupFailMessage & vbcrlf & vbcrlf
elseif (ProfileBackupMessage <> "") then
MessageBody = MessageBody & ProfileBackupMessage & vbcrlf & vbcrlf
end if
MessageBody = MessageBody & "Sent by " & wshNetwork.UserDomain & "\"& wshNetwork.UserName & " @ " & wshNetwork.ComputerName & vbcrlf & vbcrlf
objMessage.TextBody = MessageBody
'objMessage.Addattachment ""
objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
'determines whether you use local smtp (1) or network (2)
objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "SMTP.Contoso.com"
'You can find your provider's server address somewhere on the homepage or by googling for smtp server lists
objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = 1
'Determines the authentication mode. 0 for none, 1 for basic (clear text), 2 for NTLM
objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/sendusername") = "MailMan@Contoso.com"
objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/sendpassword") = "ContosoG0dMai1"
objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
'This is the default port used by most servers. Find out if yours is using a different one if there are problems
objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpusessl") = False
'Use SSL? True or False
objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpconnectiontimeout") = 60
'Maximum time connection is tried to be established
objMessage.Configuration.Fields.Update
objMessage.Send
===== 程式結束 =====
NTUserDataBackupProcess.ps1 (ContosoSendMail.exe 是一個用 VBS 寫成用來寄信的程式)
===== 程式開始 =====
$AdminEmail = "Admin@Contoso.com"
$QueuePath = "D:\UserProfileDiskBackupQueue"
$UserProfileDiskRootPath = "D:\VDI\UserProfileDisk"
$MinuteBefore = -10
function Test-FileLock {
param (
[parameter(Mandatory=$true)][string]$Path
);
$oFile = New-Object System.IO.FileInfo $Path
if ((Test-Path -Path $Path) -eq $false) {
return $false
};
try {
$oStream = $oFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
if ($oStream) {
$oStream.Close()
};
$false
} catch {
# file is locked by a process.
return $true
};
};
Get-ChildItem $QueuePath | Where {$_.LastWriteTime -gt (Get-Date).Addminutes($MinuteBefore)} | select Name | foreach {
$Info = $_.Name.Split("@")
$UserName = $Info[0]
$DomainName = $Info[1]
$CollectionName = $Info[2]
$objUser = New-Object System.Security.Principal.NTAccount($DomainName, $UserName)
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier])
$UserProfileDisk = $UserProfileDiskRootPath + "\" + $CollectionName + "\UVHD-" + $strSID.Value + ".vhdx"
$BackupSuccess = 0
if ($UserProfileDisk) {
if (Test-Path $UserProfileDisk) {
$FileLock = Test-FileLock($UserProfileDisk)
if ($FileLock -ne $True) {
Mount-DiskImage -ImagePath $UserProfileDisk
$Drive = Get-DiskImage -ImagePath $UserProfileDisk | Get-disk | Get-partition | Get-Volume | select DriveLetter,FileSystemLabel | where {$_.FileSystemLabel -eq "User Disk"} | select DriveLetter $BackupNTUserDataSourceFiles = ($Drive.DriveLetter + ":\ntuser*")
$BackupNTUserDataTargetPath = ($Drive.DriveLetter + ":\AppData\Roaming\BackupNTUserData")
if (-Not (Test-Path $BackupNTUserDataTargetPath)) {New-Item -ItemType Directory -Path $BackupNTUserDataTargetPath}
C:\Windows\System32\xcopy.exe $BackupNTUserDataSourceFiles $BackupNTUserDataTargetPath /h /y
Dismount-DiskImage -ImagePath $UserProfileDisk
Remove-Item ($QueuePath +"\" + $_.Name)
$BackupSuccess = 1
} else {
$BackupSuccess = 3
};
} else {
$BackupSuccess = 2
};
};
if ($BackupSuccess -eq 1) {
$MessageBody = "$DomainName\$UserName User Profile at Contoso $CollectionName Backup Process Complete"
} elseif ($BackupSuccess -eq 2) {
$MessageBody = "$DomainName\$UserName User Profile Disk at Contoso $CollectionName Not Found"
} elseif ($BackupSuccess -eq 3) {
$MessageBody = "$DomainName\$UserName User Profile Disk at Contoso $CollectionName is Locked"
} else{
$MessageBody = "$DomainName\$UserName User Profile at Contoso $CollectionName Backup Failed"
};
#C:\Command\ContosoSendMail.exe "ContosoMailMan" "$UserName@Contoso.com" "Contoso User Profile Backup Notifier" "$MessageBody"
C:\Command\ContosoSendMail.exe "ContosoMailMan" "$AdminEmail" "Contoso User Profile Backup Notifier" "$MessageBody"
};
Get-ChildItem $QueuePath | Where {$_.LastWriteTime -lt (Get-Date).Addminutes($MinuteBefore)} | Remove-Item
===== 程式結束 =====
UserProfileCheckAtLogon.vbs
===== 程式開始 =====
on error resume next
Set objShell = CreateObject("WScript.Shell")
Set wshNetwork = CreateObject("WScript.Network")
Set fso = CreateObject("Scripting.FileSystemObject")
Set dtmConvertedDate = CreateObject("WbemScripting.SWbemDateTime")
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
VirtualDiskModel = GetVirtualDiskModel()
UserName = wshNetwork.UserName
UserProfilePath = "C:\Users\" & wshNetwork.UserName
EnvUserProfilePath = objShell.ExpandEnvironmentStrings("%UserProfile%")
BackupExist = 0
if (LCase(UserProfilePath) <> LCase(EnvUserProfilePath)) then
Warning = "--== Warning ==--" & vbCrlf & vbCrlf
Warning = Warning & "User Profile Broken Detected: " & UserName & vbcrlf & vbcrlf
Warning = Warning & "Your User Profile seems had been broken." & vbCrlf & vbCrlf
Warning = Warning & "Press [OK] to Check If There is a Backup." & vbCrlf & vbCrlf
wscript.echo Warning
Logoff = 0
AssignUserDiskLetter = "AssignUserDiskLetter.txt"
UnAssignUserDiskLetter = "UnAssignUserDiskLetter.txt"
Set colItems = objWMIService.ExecQuery ("Select * from Win32_DiskDrive")
For Each objItem in colItems
if (UCase(objItem.Name) = "\\.\PHYSICALDRIVE1") and (UCase(objItem.Model) = VirtualDiskModel) then
Set objFile = fso.GetFile(Wscript.ScriptFullName)
ScriptPath = fso.GetParentFolderName(objFile)
' Mount UserDisk to B:
ReturnValue = objShell.Run("""C:\Windows\System32\diskpart.exe"" /s """ & ScriptPath & "\" & AssignUserDiskLetter & """",0,true)
' Check if Backup Exist
NTUserDataBackupPath = "B:\AppData\Roaming\BackupNTUserData"
if (fso.FolderExists(NTUserDataBackupPath)) then
if (fso.FileExists(NTUserDataBackupPath & "\ntuser.dat")) then
BackupExist = 1
end if
end if
exit for
end if
Next
' Restore When Backup Exist
if (BackupExist = 1) then
ObjShell.Run "cmd /c ""del """ & UserProfilePath & "\ntuser*"" /a /q /f""",0
ObjShell.Run "cmd /c """"C:\windows\system32\xcopy.exe"" """ & NTUserDataBackupPath & """ ""B:\"" /h /y""",0
Logoff = 1
end if
' UnMount UserDisk From B:
ReturnValue = objShell.Run("""C:\Windows\System32\diskpart.exe"" /s """ & ScriptPath & "\" & UnAssignUserDiskLetter & """",0,true)
Warning = "--== Warning ==--" & vbCrlf & vbCrlf
Warning = Warning & "User Profile Broken Detected: " & UserName & vbcrlf & vbcrlf
if (Logoff = 1) then
Warning = Warning & "We Found a Backup and Has Tried to Restore it." & vbCrlf
Warning = Warning & "Press [OK] to Sign-Out and Sign-In Again." & vbCrlf & vbCrlf
Warning = Warning & "If It Still Shows Broken," & vbCrlf
else
Warning = Warning & "Cannot Fix it Automatically." & vbCrlf & vbCrlf
end if
Warning = Warning & "Please contact MIS to help you fix it." & vbCrlf & vbCrlf
wscript.echo Warning
MessageBody = Warning & "Sent by " & wshNetwork.UserDomain &"\"& wshNetwork.UserName & " @ " & wshNetwork.ComputerName
MessageSubject = "User Profile Broken Detected: " & UserName
else
NTUserDataBackupPath = UserProfilePath & "\AppData\Roaming\BackupNTUserData"
if (fso.FolderExists(NTUserDataBackupPath)) then
if (fso.FileExists(NTUserDataBackupPath & "\ntuser.dat")) then
BackupExist = 1
end if
end if
MessageSubject = "Contoso Sign-in Notifier: " & UserName
MessageBody = ""
MessageBody = MessageBody & "Your account " & UserName & " was Sign-in to Workstation: " & vbcrlf & vbcrlf
MessageBody = MessageBody & "Contoso\" & wshNetwork.ComputerName & " @ " & Now & vbcrlf & vbcrlf
if (BackupExist = 1) then
MessageBody = MessageBody & "A User Profile Backup was Found." & vbcrlf & vbcrlf
else
MessageBody = MessageBody & "User Profile Backup was not Found." & vbcrlf
MessageBody = MessageBody & "It Would Be Created after Your Normally Sign-Out." & vbcrlf & vbcrlf
end if
MessageBody = MessageBody & "Sent by " & wshNetwork.UserDomain & "\"& wshNetwork.UserName & " @ " & wshNetwork.ComputerName & vbcrlf & vbcrlf
end if
AdminEmail = "UserName@Contoso.com"
Receiver = AdminEmail
'Receiver = Receiver & ";" & wshNetwork.UserName & "@Contoso.com"
MailSender = "Contoso MailMan"
Set objMessage = CreateObject("CDO.Message")
objMessage.Subject = MessageSubject
objMessage.From = MailSender & " <MailMan@Contoso.com>"
objMessage.To = Receiver
objMessage.TextBody = MessageBody
'objMessage.Addattachment ""
objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
'determines whether you use local smtp (1) or network (2)
objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "SMTP.Contoso.com"
'You can find your provider's server address somewhere on the homepage or by googling for smtp server lists
objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = 1
'Determines the authentication mode. 0 for none, 1 for basic (clear text), 2 for NTLM
objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/sendusername") = "MailMan@Contoso.com"
objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/sendpassword") = "ContosoG0dMai1"
objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
'This is the default port used by most servers. Find out if yours is using a different one if there are problems
objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpusessl") = False
'Use SSL? True or False
objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpconnectiontimeout") = 60
'Maximum time connection is tried to be established
objMessage.Configuration.Fields.Update
objMessage.Send
if (Logoff = 1) then
ObjShell.Run "cmd /c """"C:\windows\system32\logoff.exe""""",0
end if
Function GetVirtualDiskModel()
Set oss = objWMIService.ExecQuery ("Select * from Win32_OperatingSystem")
For Each os in oss
OSVersionCaption = os.Caption
VirtualDiskModel = ""
if (Len(Replace(LCase(OSVersionCaption),LCase("Windows 7"),"")) <> Len(OSVersionCaption)) then
OSVersion = "Win7"
VirtualDiskModel = "MSFT VIRTUAL DISK SCSI DISK DEVICE"
elseif (Len(Replace(LCase(OSVersionCaption),LCase("Windows 8.1"),"")) <> Len(OSVersionCaption)) then
OSVersion = "Win81"
VirtualDiskModel = "MICROSOFT VIRTUAL DISK"
elseif (Len(Replace(LCase(OSVersionCaption),LCase("Windows 10"),"")) <> Len(OSVersionCaption)) then
OSVersion = "Win10"
VirtualDiskModel = "MICROSOFT VIRTUAL DISK"
end if
Next
GetVirtualDiskModel = VirtualDiskModel
End Function
===== 程式結束 =====
AssignUserDiskLetter.txt (DiskPart 指令 Script, UserProfileCheckAtLogon.vbs 調用)
===== 程式開始 =====
select disk 1
select partition 1
assign letter=b
exit
===== 程式結束 =====
UnAssignUserDiskLetter.txt (DiskPart 指令 Script, UserProfileCheckAtLogon.vbs 調用)
===== 程式開始 =====
select disk 1
select partition 1
remove letter=b
exit
===== 程式結束 =====
沒有留言:
張貼留言