运行 以登录用户身份执行命令(远程)

Running commands as logged on user (remotely)

以为我会分享我为自己制作的这个快速功能,请根据您的需要随意调整和改进它。

有时您想 运行 作为远程计算机的登录用户执行命令。

如您所知,某些命令会为 运行 执行它的用户显示输出,如果您 运行 使用 Invoke-Command 执行相同的命令,它不会 return用户的信息,但你的)。 Get-Printer 是许多其他示例中的一个示例。

在没有任何第三方应用程序(如 PsExec 或其他应用程序)的情况下,没有简单、快速的方法 运行以本地登录用户身份执行命令,所以我制作了这个使用 VBS 的快速功能,PS1 和计划任务来实现它。

它 运行 对用户完全无提示(感谢 VBS),输出显示在您的控制台中。请注意它假设远程计算机有一个 C:\TEMP.

在 Windows 10、powershell v 5.1.17763.503 环境中创建。

我不假装它是最终的和完美的,这是我找到的最简单的方法来做需要的事情,我只是想与大家分享它,因为它非常有用!

代码解释请查看评论,随意使用。请分享您的版本,因为我很想看到人们改进它。一个好主意是让它支持多台计算机,但正如我所说,这是一个快速的功能,我没有太多时间来完善它。

也就是说,我多次使用它都没有问题:)

*输出returned是一个字符串形式,如果你想有一个合适的对象,加上'| ConvertFrom-String' 并使用它:)

请注意:获取当前登录用户名的可靠方法是通过 QWINSTA(因为 Win32_ComputerSystem - 用户名只有在用户本地登录时才是可靠的,它不会是正确的如果用户正在使用 RDP/RemoteDesktop)。所以这就是我用来获取用户名的方法,但是,请注意,在我们的法语环境中,QWINSTA 中用户名 属性 的名称是 "UTILISATEUR",因此您必须根据需要更改它(英文或其他语言)才能正常工作。如果我没记错的话,英文是"USERNAME"

这一行:

$LoggedOnUser = (qwinsta /SERVER:$ComputerName) -replace '\s{2,22}', ',' | ConvertFrom-Csv | Where-Object {$_ -like "*Acti*"} | Select-Object -ExpandProperty UTILISATEUR

请参阅下面答案中的代码。

function RunAsUser {

Param ($ComputerName,$Scriptblock)

#Check that computer is reachable
Write-host "Checking that $ComputerName is online..."

if (!(Test-Connection $ComputerName -Count 1 -Quiet)) {
Write-Host "$ComputerName is offline" -ForegroundColor Red
break
}

#Check that PsRemoting works (test Invoke-Command and if it doesn't work, do 'Enable-PsRemoting' via WMI method).
#*You might have the adjust this one to suit your environement.
#Where I work, WMI is always working, so when PsRemoting isn't, I enable it via WMI first.
Write-host "Checking that PsRemoting is enabled on $ComputerName"
if (!(invoke-command $ComputerName { "test" } -ErrorAction SilentlyContinue)) {
Invoke-WmiMethod -ComputerName $ComputerName -Path win32_process -Name create -ArgumentList "powershell.exe -command Enable-PSRemoting -SkipNetworkProfileCheck -Force" | Out-Null

    do {
    Start-Sleep -Milliseconds 200
    } until (invoke-command $ComputerName { "test" } -ErrorAction SilentlyContinue)
}


#Check that a user is logged on the computer
Write-host "Checking that a user is logged on to $ComputerName..."
$LoggedOnUser = (qwinsta /SERVER:$ComputerName) -replace '\s{2,22}', ',' | ConvertFrom-Csv | Where-Object {$_ -like "*Acti*"} | Select-Object -ExpandProperty UTILISATEUR
if (!($LoggedOnUser) ) {
Write-Host "No user is logged on to $ComputerName" -ForegroundColor Red
break
}


#Creates a VBS file that will run the scriptblock completly silently (prevents the user from seeing a flashing powershell window)
@"
Dim wshell, PowerShellResult
set wshell = CreateObject("WScript.Shell")
Const WindowStyle = 0
Const WaitOnReturn = True
For Each strArg In WScript.Arguments
arg = arg & " " & strArg
Next 'strArg
PowerShellResult = wshell.run ("PowerShell " & arg & "; exit $LASTEXITCODE", WindowStyle, WaitOnReturn)
WScript.Quit(PowerShellResult)
"@ | out-file "\$ComputerName\C$\TEMP\RAU.vbs" -Encoding ascii -force

#Creates a script file from the specified '-Scriptblock' parameter which will be ran as the logged on user by the scheduled task created below.
#Adds 'Start-Transcript and Stop-Transcript' for logging the output.
$Scriptblock = "Start-Transcript C:\TEMP\RAU.log -force" + $Scriptblock + "Stop-Transcript"
$Scriptblock | out-file "\$ComputerName\C$\TEMP\RAU.ps1" -Encoding utf8  -force

#On the remote computer, create a scheduled task that runs the .ps1 script silently in the user's context (with the help of the vbs)
Write-host "Running task on $ComputerName..."
Invoke-Command -ComputerName $ComputerName -ArgumentList $LoggedOnUser -ScriptBlock {
    param($loggedOnUser)

    $SchTaskParameters = @{
    TaskName = "RAU"
    Description = "-"
    Action = (New-ScheduledTaskAction -Execute "wscript.exe" -Argument "C:\temp\RAU.vbs C:\temp\RAU.ps1")
    Settings = (New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -DontStopOnIdleEnd)
    RunLevel = "Highest"
    User = $LoggedOnUser
    Force = $true
    }

    #Register and Start the task
    Register-ScheduledTask @SchTaskParameters | Out-Null
    Start-ScheduledTask -TaskName "RAU"

    #Wait until the task finishes before continuing
    do {
    Write-host "Waiting for task to finish..."
    $ScheduledTaskState = Get-ScheduledTask -TaskName "RAU" | Select-Object -ExpandProperty state
    start-sleep 1
    } until ( $ScheduledTaskState -eq "Ready" )

    #Delete the task
    Unregister-ScheduledTask -TaskName "RAU" -Confirm:$false
}
Write-host "Task completed on $ComputerName"      
#Grab the output of the script from the transcript and remove the header (first 19) and footer (last 5)
$RawOutput = Get-Content "\$ComputerName\C$\temp\RAU.log" | Select-Object -Skip 19
$FinalOutput = $RawOutput[0..($RawOutput.length-5)]

#Shows output
return $FinalOutput


#Delete the output file and script files
Remove-Item "\$ComputerName\C$\temp\RAU.log" -force
Remove-Item "\$ComputerName\C$\temp\RAU.vbs" -force
Remove-Item "\$ComputerName\C$\temp\RAU.ps1" -force

}

#____________________________________________________

#Example command
#Note: Sometimes Start-Transcript doesn't show the output for a certain command, so if you run into empty output, add: ' | out-host' or '| out-default' at the end of the command not showing output.
$Results = RunAsUser -ComputerName COMP123 -Scriptblock {
get-printer | Select-Object name,drivername,portname | Out-host
}


$Results

#If needed, you can turn the output (which is a string for the moment) to a proper powershell object with ' | ConvertFrom-String'