WPF PowerShell应用切换用户ID时无响应,求Runspace改造或优化方案
解决WPF PowerShell应用无响应问题:Runspace集成方案
你的WPF应用无响应是因为所有耗时操作(AD查询、WMI调用、文件读取)都在UI线程执行,导致界面阻塞。下面是集成Runspace的完整修改脚本,将耗时任务放到后台线程执行,同时保证UI实时响应:
## Add .xaml file, remove errors and set variables to use them in this script Add-Type -AssemblyName PresentationFramework $xamlFile="\\zeus\a\lrc\GROUPS\WCO\USR\STU_DATA\EXPERT\EXPERT\ServiceDesk_guide\SCript\SD-Tool\MainWindow.xaml" $inputXAML = Get-Content -Path $xamlFile -Raw $inputXAML = $inputXAML -replace 'mc:Ignorable="d"',''-replace "x:N","N" -replace '^<Win.*','<Window' [XML]$XAML =$inputXAML $reader = New-Object System.Xml.XmlNodeReader $XAML try{ $psform = [Windows.Markup.XamlReader]::Load($reader) }catch{ Write-host $_.Exception throw } $xaml.SelectNodes("//*[@Name]") | ForEach-Object { Try{ Set-Variable -Name "var_$($_.Name)" -value $psform.FindName($_.Name) -ErrorAction stop }catch{ throw } } # 创建Runspace池,用于后台执行耗时任务 $runspacePool = [RunspaceFactory]::CreateRunspacePool(1, 2) $runspacePool.ApartmentState = [System.Threading.ApartmentState]::STA $runspacePool.Open() ## 点击搜索按钮执行后台任务 $var_OnClick.Add_Click({ # 禁用搜索按钮,显示加载状态 $var_OnClick.IsEnabled = $false $var_OnClick.Content = "查询中..." # 获取当前输入的用户名,后台线程不能直接访问UI控件 $targetUserName = $var_userName.Text.Trim() if (-not $targetUserName) { [System.Windows.MessageBox]::Show("请输入用户ID") $var_OnClick.IsEnabled = $true $var_OnClick.Content = "搜索" return } # 定义后台执行的脚本块 $backgroundScript = { param($userName, $wwg00m, $dedicatedSb) #Params $Computernames = Get-Content -Path "\\zeus\a\lrc\GROUPS\WCO\USR\STU_DATA\EXPERT\EXPERT\ServiceDesk_guide\SCript\Data\Computers in a specific workgroup or domain.csv" | Select-Object -Skip 3 | ConvertFrom-Csv -Delimiter "," $pcNamecsv = $Computernames.Where({ $_.Details_Table0_User_Name0 -eq $userName}).Details_Table0_Name0 | Where { $_ -like 'BNL0*' -or $_ -like 'BNL5*'} $Mailboxrights = Import-csv "\\zeus\a\lrc\GROUPS\WCO\USR\STU_DATA\EXPERT\EXPERT\ServiceDesk_guide\SCript\Data\mailboxRights2.csv" -delimiter "|" # if hostname >1 search for hostname last used $ADcomputing = @('Name','LastLogonDate') $ADproperty = foreach ($PC in $pcNamecsv) {Get-ADcomputer $PC -Properties $ADcomputing} $pcName = ($ADproperty | Sort LastLogonDate -Descending | Select -First 1).Name # Search for IP address with nslookup $ipAddress = (nslookup $pcName | Select-String Address | Where-Object LineNumber -eq 5).ToString().Split(' ')[-1] # find properties in Active Directory $adProperties = @( 'Name','mail','OfficePhone','LockedOut','Enabled', 'msDS-UserPasswordExpiryTimeComputed','AccountExpirationDate','Givenname','DisplayName' ) $wwgUser = Get-ADuser -Identity $userName -Server $wwg00m -Properties $adProperties $agfUser = Get-ADuser -Identity $userName -Properties $adProperties $agfExpiry = Get-ADUser -identity $userName –Properties "msDS-UserPasswordExpiryTimeComputed" | Select-Object -Property @{Name="ExpiryDate";Expression={[datetime]::FromFileTime($_.msDS-UserPasswordExpiryTimeComputed)}} | select -expandproperty "ExpiryDate" -first 1 $wwgExpiry = Get-ADUser -identity $userName -server $wwg00m –Properties "msDS-UserPasswordExpiryTimeComputed" | Select-Object -Property @{Name="ExpiryDate";Expression={[datetime]::FromFileTime($_.msDS-UserPasswordExpiryTimeComputed)}} | select -expandproperty "ExpiryDate" -first 1 # Check if VPN connection was made before accessing Windows environment $windowsLogon = try {(Get-Childitem -path \\$pcName\c$\Users\*\AppData\Local\Temp\logon.txt -ErrorAction Stop | Sort "LastWriteTime" -Descending | Select -First 1).LastWriteTime} catch {$null} $vpnLogon = (Get-ADuser $userName -server mgm.agf.be -properties Modified).Modified # Find properties in Active Directory from nested AVC groups $SID = (Get-ADuser $userName -Properties SID).SID -replace 'SID','' $AVCSEC = (Get-ADGroup -LDAPFilter "(member=CN=$SID,CN=ForeignSecurityPrincipals,DC=wwg00m,DC=rootdom,DC=net)" ` -Server wwg00m.rootdom.net -Properties Name).Name $DedicatedProperties = @('Name','Description') $dedicatedClients = Get-ADcomputer -filter * -SearchBase $dedicatedSb -server $wwg00m -properties $DedicatedProperties $dedicatedclientName = $dedicatedClients.Where({ $_.Description -Match $SID}).Name # Shared mailbox properties $Mailbox = $Mailboxrights.Where({$_.Trustee -match $userName}) | Select-Object "Display Name", "Email Address", "Rights","Domain" # Computer information, hostname obtained from csv list $model = try {(Get-CimInstance -Class Win32_ComputerSystem -ComputerName $pcName -ea Stop ).Model} catch {$null} $osVersion = try {(Get-CimInstance win32_operatingsystem -ComputerName $pcName -ea Stop ).Version} catch {$null} $osSystem = try {(Get-ADcomputer $pcName -properties operatingsystem -ea Stop ).operatingsystem} catch {$null} # Bitlocker recover key $objComputer = Get-ADComputer $pcName $Bitlocker_Object = Get-ADObject -Filter {objectclass -eq 'msFVE-RecoveryInformation'} -SearchBase $objComputer.DistinguishedName -Properties 'msFVE-RecoveryPassword' $bitlockerKey = ($Bitlocker_Object | select msFVE-RecoveryPassword).msFVE-RecoveryPassword # 返回所有需要更新UI的数据 [PSCustomObject]@{ agfUser = $agfUser wwgUser = $wwgUser agfExpiry = $agfExpiry wwgExpiry = $wwgExpiry pcName = $pcName ipAddress = $ipAddress windowsLogon = $windowsLogon vpnLogon = $vpnLogon AVCSEC = $AVCSEC dedicatedclientName = $dedicatedclientName Mailbox = $Mailbox model = $model osVersion = $osVersion osSystem = $osSystem bitlockerKey = $bitlockerKey } } # 创建后台任务 $psJob = [PowerShell]::Create().AddScript($backgroundScript).AddParameters(@{ userName = $targetUserName wwg00m = "wwg00m.rootdom.net" dedicatedSb = "OU=vClientD,OU=AZ-BENE,OU=E2,OU=VDI,OU=Prod,OU=AVC,OU=Clients,DC=wwg00m,DC=rootdom,DC=net" }) $psJob.RunspacePool = $runspacePool # 异步执行任务,完成后更新UI $asyncResult = $psJob.BeginInvoke() $waitHandle = $asyncResult.AsyncWaitHandle $waitHandle.WaitOne() | Out-Null try { $resultData = $psJob.EndInvoke($asyncResult) if ($resultData) { # 回到UI线程更新控件,WPF控件只能在创建线程操作 $psform.Dispatcher.Invoke([Action]{ # Main Tabitem $var_fullname.Text = $resultData.agfUser.DisplayName $var_phone.Text = $resultData.agfUser.OfficePhone $var_hostname.Text = $resultData.pcName $var_mail.Text = $resultData.agfUser.mail $var_enabled.Text = $resultData.agfUser.enabled $var_exp_acc.Text = $resultData.agfUser.AccountExpirationDate $var_exp_pass.Text = $resultData.agfExpiry $var_locked.Text = $resultData.agfUser.LockedOut # wwg00m Tabitem $var_enabled_wwg00m.Text = $resultData.wwgUser.enabled $var_exp_acc_wwg00m.Text = $resultData.wwgUser.AccountExpirationDate $var_exp_pass_wwg00m.Text = $resultData.wwgExpiry $var_locked_wwg00m.Text = $resultData.wwgUser.LockedOut # Computer Tabitem $var_osName.Text = $resultData.osVersion $var_model.Text = $resultData.model $var_iAddress.Text = $resultData.ipAddress $var_bkey.Text = $resultData.bitlockerKey $var_cName.Text = $resultData.pcName $var_vName.Text = $resultData.osSystem # Used in more then 1 Tabitem $var_vpnLog.Text = $resultData.vpnLogon $var_windowsLog.Text = $resultData.windowsLogon $var_ipCurrent.Text = $resultData.ipAddress # AVC Tabitem $var_Pooled.Text = if ($resultData.AVCSEC -like "AVC-P-POOL-AZBENE-VDIW10*") {"Available"} else {"Not available"} $var_Dedicated.Text = if ($resultData.AVCSEC -like "AVC-P-POOL-AZBENE-DCW10*") {"Available"} else {"Not available"} $var_uat.Text = if ($resultData.AVCSEC -like "AVC-QS-POOL-AZBENE-VDIW10*") {"Available"} else {"Not available"} $var_poolRing0.Text = if ($resultData.AVCSEC -like "AVC-P-POOL-AZBENE-RING0-VDIWIN10") {"Available"} else {"Not available"} $var_dedicatedRing0.Text = if ($resultData.AVCSEC -like "AVC-P-POOL-AZBENE-RING0-DEDWIN10") {"Available"} else {"Not available"} $var_adminLight.Text = if ($resultData.AVCSEC -like "AVC-P-CONF-DCW10-AdminLight") {"Available"} else {"Not Available"} $var_hostNamededicated.Text = $resultData.dedicatedclientName # DataGrid2 Mail Tabitem $var_dataGrid2.ItemsSource = $resultData.Mailbox # 重置搜索按钮状态 $var_OnClick.IsEnabled = $true $var_OnClick.Content = "搜索" }) } } catch { $psform.Dispatcher.Invoke([Action]{ [System.Windows.MessageBox]::Show("查询出错: $_") $var_OnClick.IsEnabled = $true $var_OnClick.Content = "搜索" }) } finally { $psJob.Dispose() } }) # unlock agf account button $var_unlockAGF.Add_Click{ $userName = $var_userName.Text.Trim() if (-not $userName) { [System.Windows.MessageBox]::Show("请输入用户ID") return } Unlock-ADAccount -Identity $userName Start-Sleep -Seconds 2 [System.Windows.MessageBox]::Show('Account has been unlocked') } ## DataGrid and Dropdownlist Tabitem: Access $var_comboBox.Text = $null $var_dataGrid.ItemsSource = $null $var_comboBox.Add_SelectionChanged({ $userName = $var_userName.Text.Trim() if (-not $userName) { $var_dataGrid.ItemsSource = $null return } # 同样用Runspace处理下拉选择的耗时查询 $var_comboBox.IsEnabled = $false $wwg00m = "wwg00m.rootdom.net" $backgroundScript = { param($userName, $wwg00m) $agfGroups = (Get-ADuser -identity $userName -Properties MemberOf).MemberOf -Replace 'CN=', '' -Replace ',.*', '' $wwg00mGroups = (Get-ADuser -identity $userName -server $wwg00m -Properties MemberOf).MemberOf -Replace 'CN=', '' -Replace ',.*', '' $sid = (Get-ADuser -identity $userName -Properties SID).SID -replace 'SID','' $avcGroups = (Get-ADGroup -LDAPFilter "(member=CN=$sid,CN=ForeignSecurityPrincipals,DC=wwg00m,DC=rootdom,DC=net)" ` -Server $wwg00m -Properties Name).Name [PSCustomObject]@{ agfGroups = $agfGroups wwg00mGroups = $wwg00mGroups avcGroups = $avcGroups } } $psJob = [PowerShell]::Create().AddScript($backgroundScript).AddParameters(@{userName=$userName; wwg00m=$wwg00m}) $psJob.RunspacePool = $runspacePool $asyncResult = $psJob.BeginInvoke() $waitHandle = $asyncResult.AsyncWaitHandle $waitHandle.WaitOne() | Out-Null try { $resultData = $psJob.EndInvoke($asyncResult) $psform.Dispatcher.Invoke([Action]{ $item = $var_comboBox.SelectedItem.Content if ($item -eq "wwg00m") { $var_dataGrid.ItemsSource = $resultData.wwg00mGroups } elseif ($item -eq "agf.be" ) { $var_dataGrid.ItemsSource = $resultData.agfGroups } elseif ($item -eq "AVC" ) { $var_dataGrid.ItemsSource = $resultData.avcGroups } $var_comboBox.IsEnabled = $true }) } catch { $psform.Dispatcher.Invoke([Action]{ [System.Windows.MessageBox]::Show("查询组信息出错: $_") $var_comboBox.IsEnabled = $true }) } finally { $psJob.Dispose() } }) ## Connect to host with msra "Remote assistance" $var_connectHost.Add_Click({ $pcName = $var_hostname.Text if ($pcName) { Start-Process -FilePath "C:\Windows\System32\msra.exe" -Args "/Offerra \\$pcName" } else { [System.Windows.MessageBox]::Show("请先查询用户信息获取主机名") } }) ## show GUI $psform.ShowDialog() # 关闭Runspace池 $runspacePool.Close() $runspacePool.Dispose()
关键改动说明
- Runspace池管理:创建固定大小的Runspace池,复用后台线程,避免频繁创建销毁线程的开销
- UI线程隔离:后台任务只负责数据查询,所有UI控件更新通过
Dispatcher.Invoke回到UI线程执行,符合WPF线程安全要求 - 加载状态反馈:搜索按钮在查询过程中禁用并显示"查询中...",让用户感知到后台正在处理
- 错误处理:后台任务异常时捕获并提示用户,同时恢复按钮状态
- 替换WMI为CIM:
Get-CimInstance比`Get
相关产品推荐
相关产品推荐

