如何在没有错误消息或异常的情况下调试 PowerShell 进程
Posted
技术标签:
【中文标题】如何在没有错误消息或异常的情况下调试 PowerShell 进程【英文标题】:How to debug PowerShell process whithout error message or exception 【发布时间】:2018-08-12 04:23:01 【问题描述】:我正在尝试从我的 .NET 应用程序中运行以下 PowerShell 脚本:
try
Start-Process "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" -ArgumentList "--headless --disable-gpu --print-to-pdf=c:\myDir\file.pdf https://www.bing.com"
$x = "Complete"
$x | Out-File C:\myDir\error.txt
Catch
$_ | Out-File C:\myDir\error.txt
简单地说,上面会创建一个基于bing.com网站的pdf
在我的开发环境中,它作为 PowerShell 脚本运行良好。它还在生产服务器上运行良好(同样,作为 PowerShell 脚本)。
当我从生产服务器上的 Web 应用程序调用此 PowerShell 脚本时,会出现此问题。我的 C# 代码是
var command = "c:\myDir\ps.ps1";
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "powershell.exe";
psi.Arguments = command;
Process process = new Process();
process.StartInfo = psi;
process.Start();
这在我的开发机器上运行良好。它在生产服务器上失败。 error.txt
文件已写入光盘,这表明这不是权限问题。但是,error.txt
文件的内容始终显示“完整”。它永远不会出错。
因此,PowerShell 脚本中的问题似乎永远不会被击中。因此,没有错误消息。 C# 代码中没有抛出异常。无论如何,它不起作用。
如何调试?
或者,如果更简单的话,我很乐意直接运行代码而不是调用 PowerShell 脚本文件,但以下代码也“什么都不做”。
var command = $"\"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe\" -ArgumentList \"--no-sandbox --headless --disable-gpu --print-to-pdf=imagePath fullUrl";
【问题讨论】:
一方面,您可以通过从等式中删除 PowerShell 来简化它 - 只需使用 Process.Start 直接启动 Chrome。然后,你可以开始重定向进程stdout和stderr,看看是否有错误发生。 我不完全理解@RB。我已经可以证明脚本正在运行,因为它总是更新(或创建)error.txt
文件。我还可以运行实际的 powershell 脚本,它按预期执行
调试的第一条规则 - 简化您的问题...此时您的代码中有一层间接性。删除它并直接记录 stdout 和 stderr 可能会公开其他信息,可以帮助您解决问题。当然,它可能不会 - 但检查大约需要 30 分钟,到目前为止你已经等了一周:)
【参考方案1】:
我能够重现您的问题。这是由于您的生产服务器上的 Web 应用程序在当前未登录的用户下运行。它在分配的应用程序池的身份下运行。如果 Chrome 是在与当前登录用户不同的用户下启动的,则 Chrome 有known issue 无法正常工作。如果您检查该链接,您将看到该问题已在 2012 年 12 月注册,但仍未解决。如果在不同用户下启动 Chrome,您可以轻松重现该问题(按下 Shift 调用快捷上下文菜单中的“以不同用户身份运行”)。在这种情况下,Chrome 不会打开任何页面,只会显示灰屏。
workaround 是用--no-sandbox
开关启动 Chrome。谷歌实际上并不推荐这一点。但是,如果您以自动方式运行 Chrome 以访问受信任的来源,我相信没问题。
所以要解决这个问题,请按以下方式修改脚本中的start-process
:
start-process "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" -ArgumentList "--no-sandbox --headless --disable-gpu --print-to-pdf=c:\myDir\file.pdf https://www.bing.com"
更新
一开始我低估了这个问题。现在,经过额外的研究和许多尝试过的方法,我可以提出可行的解决方案。
我没有解决您当前从 Web 应用程序直接启动 powershell 和 chrome 的方法。 Chrome 无法启动,并且事件日志中出现以下错误:
Faulting application name: chrome.exe, version: 64.0.3282.186, time stamp: 0x5a8e38d5
Faulting module name: chrome_elf.dll, version: 64.0.3282.186, time stamp: 0x5a8e1e3d
Exception code: 0x80000003
Fault offset: 0x00000000000309b9
Faulting process id: 0x11524
Faulting application start time: 0x01d3bab1a89e3b4f
Faulting application path: C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
Faulting module path: C:\Program Files (x86)\Google\Chrome\Application\64.0.3282.186\chrome_elf.dll
Report Id: e70a5a36-26a4-11e8-ac26-b8ca3a94ba80
即使您将应用程序池配置为使用可以启动 chrome 的某些现有(普通)用户的身份,也会发生此错误。 可能可以配置 IIS 或应用程序池来防止这些错误,但我还没有找到方法。
我的建议是从启动 powershell 进程从控制器操作切换到使用 Windows 任务调度程序调度任务。
以下是完成此任务应采取的步骤:
-
在您的生产服务器上创建一个用户,Chrome 将在该用户下启动。我将创建的用户称为“testuser”。
在
testuser
下登录,启动chrome,打开一些站点。如果没有这一步,流程不会成功,可能是因为缺少 chrome 用户帐户。
为testuser
授予“作为批处理作业登录”权限。此步骤是成功执行testuser
下的计划任务所必需的。该过程在this answer 中进行了描述
将--no-sandbox
参数添加到脚本中,正如我在最初的回答中所描述的那样。
将Process.Start()
的代码替换为任务作业的调度。
The easiest way 通过TaskScheduler NuGet 从 .Net 安排任务。将其安装到您的应用程序并添加以下代码:
string powerShellScript = @"c:\myDir\ps.ps1";
string userName = @"YOURCOMP\testuser";
string userPassword = "TestPwd123";
using (TaskService ts = new TaskService())
TaskDefinition td = ts.NewTask();
td.Triggers.Add(new RegistrationTrigger
StartBoundary = DateTime.Now,
EndBoundary = DateTime.Now.AddMinutes(1),
);
td.Settings.DeleteExpiredTaskAfter = TimeSpan.FromSeconds(5);
td.Actions.Add(new ExecAction("powershell.exe", powerShellScript));
ts.RootFolder.RegisterTaskDefinition($@"Print Pdf - Guid.NewGuid()", td, createType: TaskCreation.Create, userId: userName, password: userPassword, logonType: TaskLogonType.Password);
在上面的代码中,sn -p 更改testuser
的名称和密码。
通过这种方法,您的脚本已成功执行,并且 pdf 已成功打印。
由 OP 更新
如果上述操作仍然失败,请再次检查事件查看器日志。在这种情况下,我遇到了与The machine-default permission settings do not grant Local Activation permission for the COM Server application with CLSID 20FD4E26-8E0F-4F73-A0E0-F27B8C57BE6F and APPID Unavailable
类似的消息的问题,但通过授予permissions for the CLSID 已解决。此外,尝试在任务计划程序中自行运行任务,例如创建一个新任务以简单地启动记事本或类似的,以确保这与您要测试的帐户一起使用。就我而言,我必须使用管理员帐户。
【讨论】:
谢谢。大约 10 小时后我会在办公室进行测试!显然我会回来,但已经 +1 刚试过。可惜没有影响。同样的问题仍然存在 应用程序在生产环境中运行的池的标识是什么?是本地系统帐号吗? 应用程序池标识为ApplicationPoolIdentity
这似乎是一种非常聪明的方法。代码是否创建了一次性任务但留下了它(意思是,看起来好像没有清理操作)【参考方案2】:
我认为除了 CodeFuller 所说的没有带有--no-sandbox
选项的沙箱之外,您还应该禁用所有扩展、同步和书签。
最好有一个 Guest session 别名“无需登录即可浏览”,并带有--bwsi
选项。
有趣的是,在测试过程中,我发现在执行--bwsi
之前使用--disable-extensions
明确禁用扩展会更好,得到更好的pdf 打印输出。
我已经对其进行了测试,对我来说它有效。我期待您的反馈。
Edit1 和 Edit3 - 删除 try...catch 并添加用户和密码并添加 psuser 细节
您可能在域上,所以我调整了脚本以在域上以不同用户身份运行(用户必须具有正确的权限!)
首先创建您的凭据文件:
登录到用户,例如psuser
创建密码文件:
# Encrypt user password and save it to file
Read-Host -AsSecureString | ConvertFrom-SecureString | Out-File 'C:\<your_path>\your_secret_password.txt'
然后使用加密凭据运行以下改进的脚本:
$username = 'psuser' # This needs to be adjusted to correct user you are using
$domain = <your_domain> # adjust to your needs
$encrypted_passwd = get-content 'C:\<your_path>\your_secret_password.txt' | ConvertTo-securestring
# Setting process invocation parameters.
$process_start_info = New-Object -TypeName System.Diagnostics.ProcessStartInfo
$process_start_info.CreateNoWindow = $true
$process_start_info.UseShellExecute = $false
$process_start_info.RedirectStandardOutput = $true
$process_start_info.RedirectStandardError = $true
$process_start_info.UserName = $username
$process_start_info.Domain = $domain
$process_start_info.Password = $encrypted_passwd
$process_start_info.Verb = 'runas'
$process_start_info.FileName = 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
$process_start_info.Arguments = '--no-sandbox --disable-extensions --bwsi --headless --disable-gpu --print-to-pdf=C:\prg\PowerShell\test\chrome_file.pdf https://www.bing.com'
# Creating process object.
$process = New-Object -TypeName System.Diagnostics.Process
$process.StartInfo = $process_start_info
# Start the process
[Void]$process.Start()
$process.WaitForExit()
# synchronous output - captures everything
$output = $process.StandardOutput.ReadToEnd()
$output += $process.StandardError.ReadToEnd()
Write-Output $output
在脚本调试过程中我遇到了这些错误:
a) 当您想针对 AD 服务器进行验证但它不可用时:
Exception calling "Start" with "0" argument(s): "There are currently no logon servers available to service the logon request"
At C:\prg\PowerShell\test\chrome_print.ps1:56 char:12
+ [Void]$process.Start()
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : Win32Exception
Exception calling "WaitForExit" with "0" argument(s): "No process is associated with this object."
At C:\prg\PowerShell\test\chrome_print.ps1:58 char:12
+ $process.WaitForExit()
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : InvalidOperationException
You cannot call a method on a null-valued expression.
At C:\prg\PowerShell\test\chrome_print.ps1:61 char:12
+ $output = $process.StandardOutput.ReadToEnd()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\prg\PowerShell\test\chrome_print.ps1:62 char:12
+ $output += $process.StandardError.ReadToEnd()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
b) 脚本中缺少域信息:
Exception calling "Start" with "0" argument(s): "The stub received bad data"
At C:\prg\PowerShell\test\chrome_print.ps1:39 char:12
+ [Void]$process.Start()
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : Win32Exception
Exception calling "WaitForExit" with "0" argument(s): "No process is associated with this object."
At C:\prg\PowerShell\test\chrome_print.ps1:41 char:12
+ $process.WaitForExit()
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : InvalidOperationException
You cannot call a method on a null-valued expression.
At C:\prg\PowerShell\test\chrome_print.ps1:44 char:12
+ $output = $process.StandardOutput.ReadToEnd()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\prg\PowerShell\test\chrome_print.ps1:45 char:12
+ $output += $process.StandardError.ReadToEnd()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
打印 pdf
和 stderr 消息:
[0313/112937.660:ERROR:gpu_process_transport_factory.cc(1009)] Lost UI shared context.
[0313/112937.662:ERROR:instance.cc(49)] Unable to locate service manifest for metrics
[0313/112937.662:ERROR:service_manager.cc(890)] Failed to resolve service name: metrics
[0313/112938.152:ERROR:instance.cc(49)] Unable to locate service manifest for metrics
[0313/112938.153:ERROR:service_manager.cc(890)] Failed to resolve service name: metrics
[0313/112942.876:INFO:headless_shell.cc(566)] Written to file C:\prg\PowerShell\test\chrom e_file.pdf.
Edit2 使用 ASP.NET
添加 windows 帐户模拟使用 ASP.NET 模拟 Windows 帐户:
ASP.NET 用户未传递到新线程(默认情况下)。当您想调用 PowerShell 脚本时,它会在具有不同凭据的其他线程中调用(当您有专门的域认证用户来运行上述脚本时,您可以使用上述脚本克服这一点)。默认脚本在内置账号NT AUTHORITY\NETWORK SERVICE
下执行。
这些步骤是在ASP.NET
级别克服它:
1) 在 IIS 中启用 Windows 身份验证 a) 先安装(这是windows 2008 R2截图):
b) 在您的 IIS 上启用它:
将其更改为启用:
2) 更改您网站的 web.config 以正确处理模拟
编辑网站目录中的 web.config 文件。为了执行当前用户的安全上下文(AD)的服务器端代码。
找到xml标签:<system.web>
并添加两个新元素以启用windows authentication
<authentication mode="Windows" />
<identity impersonate="True" />
3) 正确编写代码以调用进程内 PowerShell 脚本
您需要调整您的 ASP.NET 代码,使您拥有 powershell 运行空间,并在管道中调用运行空间内的脚本
一个简单的例子:
// You need to create a Runspace. Each other pipeline you create will run in the same Runspace
// Do it only once, all others will be pipelined
RunspaceConfiguration powershellConfiguration = RunspaceConfiguration.Create();
var powershellRunspace = RunspaceFactory.CreateRunspace(powershellConfiguration);
powershellRunspace.Open();
// create a pipeline the cmdlet invocation
using ( Pipeline psPipeline = powershellRunspace.CreatePipeline() )
// Define the command to be executed in this pipeline
Command script = new Command("PowerShell_script");
// Add any parameter(s) to the command
script.Parameters.Add("Param1", "Param1Value");
// Add it to the pipeline
psPipeline.Commands.Add(script);
try
// Invoke() the script
var results = psPipeline.Invoke();
// work with the results
catch (CmdletInvocationException exception)
// Any exceptions here - for the invoked process
4) 修改aspnet.config
允许模拟跨线程
此步骤允许您以当前的模拟用户身份运行。
您必须修改服务器的 aspnet.config
文件。
在configuration
和runtime
中添加两个xml元素:
<configuration>
<runtime>
...
<legacyImpersonationPolicy enabled="true" />
<alwaysFlowImpersonationPolicy enabled="false" />
</runtime>
</configuration>
【讨论】:
可悲的是,同样的问题仍在继续:s @MyDaftQuestions 你能告诉我你的标准输出和标准错误吗? 我可以,但请给我大约 8 小时,直到我可以测试它 还有一个问题。您在哪个用户下在生产环境中运行它? 我不知道@tukan 的答案。我怎么知道?我只是发布网站,它似乎只是工作。如果您的意思是在 IIS 中,它设置为在 ApplicationPoolIdentity 下运行【参考方案3】:您必须重定向标准输入和标准输出,以便它从 powershell.exe 将其发送回父进程(您的网络应用程序)。我修改了你的代码示例来做到这一点:
var command = "c:\myDir\ps.ps1";
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "powershell.exe";
psi.Arguments = command;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
Process process = new Process();
process.StartInfo = psi;
process.Start();
process.WaitForExit();
Console.WriteLine(process.StandardOutput);
Console.WriteLine(process.StandardError);
【讨论】:
你的意思是process.StandardError
虽然我发现这很有用,但并没有帮助。如果您看到@CodeFuller 帖子,他会提到异常被抛出并登录到事件查看器中。因此,这篇文章将无济于事,因为 Chrome 正在大肆宣传是有原因的以上是关于如何在没有错误消息或异常的情况下调试 PowerShell 进程的主要内容,如果未能解决你的问题,请参考以下文章
如何在 C++Builder 发布模式下找到异常,但不是调试