从 CMD/CLI 调用时 Powershell 脚本的奇怪行为 [英] Powershell script strange behaviour when invoked from CMD/CLI

查看:53
本文介绍了从 CMD/CLI 调用时 Powershell 脚本的奇怪行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

从 Powershell 控制台执行时,此脚本工作正常...
但从 CMD.exe 使用 Powershell.exe 执行时不起作用...
(powershell.exe -file script.ps1,使用 Powershell 5.1.17763.771)

# 显示 Windows Shell 文件夹属性$App = New-Object -ComObject Shell.Application;$AppNS = $App.NameSpace("c:\windows");$AppNS.Self.InvokeVerb("属性");

我测试了其他 GUI 对象(Winforms 和 WPF)
他们工作得很好...

?有什么想法...

解决方案

问题在于您正在创建的进程内 COM 对象在调用进程退出时超出范围,这在您的情况,当通过 PowerShell 的 CLI 从 cmd.exe 调用时,意味着该窗口通常甚至没有机会显示或在非常短暂的出现后自动关闭.

  • 交互式 PowerShell 会话中,该进程在退出脚本后继续存在 - 这就是您的代码在那里工作的原因.

  • 当您通过 PowerShell 的 CLI(powershell.exe for Windows PowerShellpwsh 用于 PowerShell Core,如果没有 -NoExit 开关来使进程无限期地保持活动状态),PowerShell 进程在脚本终止时退出.

<小时>

使用 -NoExit 充其量只是一个权宜之计,因为它会使 PowerShell 进程无限期,即使您可能希望它只存活那么长时间当属性"对话框窗口打开时 - 每当用户选择关闭它.

因此,您需要同步等待 (a) 属性对话框窗口打开,然后 (b) 在退出脚本之前等待它关闭.

您可以借助 .NET UI 自动化 库如下;请注意,代码使用 PowerShell v5+ 语法:

使用命名空间 System.Windows.Automation# 加载 UI 自动化客户端程序集.# 需要 Windows PowerShell 或 PowerShell Core v7+(仅适用于 Windows).添加类型 -AssemblyName UIAutomationClient;添加类型 -AssemblyName UIAutomationTypes# 开始显示 Windows 文件夹的属性对话框.$App = New-Object -ComObject Shell.Application$AppNS = $App.NameSpace('c:\windows')$AppNS.Self.InvokeVerb('属性')# 注释掉这一行以抑制冗长的消息.$VerbosePreference = '继续'Write-Verbose 'Wating for the window' 的创建......'做 {# 在当前进程的顶级窗口中搜索一个窗口# 类名'#32770',这是属性对话框窗口# 使用(不知道为什么,但随着时间的推移它一直很稳定).$w = [AutomationElement]::RootElement.FindFirst([TreeScope]::Children,[AndCondition]::new([PropertyCondition]::new([AutomationElement]::ClassNameProperty, '#32770'),[PropertyCondition]::new([AutomationElement]::ProcessIdProperty, $PID)))Start-Sleep -Milliseconds 100} while (-not $w)Write-Verbose '窗口已经出现,正在等待它关闭......'而($w.Current.ProcessId){Start-Sleep -Milliseconds 100}Write-Verbose '窗口现在关闭,继续前进.# 此时,如果脚本是通过 PowerShell 的 CLI 调用的(powershell.exe -file ...)# PowerShell 进程终止.

现在,从批处理文件中按如下方式调用 PowerShell 脚本将弹出属性"对话框并等待它关闭然后继续:

@echo off:: # ... 你的批处理文件:: # 弹出属性对话框并*等待它关闭*.powershell.exe -file script.ps1:: # ...

相比之下,如果您只想在继续运行批处理文件的同时启动属性"对话框(请务必先禁用详细消息):

:: # 仅*启动*属性对话框的显示和*继续执行*.启动/B powershell.exe -file script.ps1

This scripts works fine when executed from Powershell console...
but does not work when executed with Powershell.exe from CMD.exe...
(powershell.exe -file script.ps1, using Powershell 5.1.17763.771)

# display Windows Shell Folder propertes
$App  = New-Object -ComObject Shell.Application;
$AppNS = $App.NameSpace( "c:\windows" );
$AppNS.Self.InvokeVerb( "Properties" );

I tested other GUI objects (Winforms & WPF)
and they work fine...

?any ideas...

解决方案

The problem is that the in-process COM object you're creating goes out of scope when the calling process exits, which in your case, when called from cmd.exe via PowerShell's CLI, means that the window typically never even gets a chance to display or is automatically closed after a very brief appearance.

  • In an interactive PowerShell session, the process lives on after exiting the script - that's why your code works there.

  • When you invoke a script via via PowerShell's CLI (powershell.exe for Windows PowerShell, pwsh for PowerShell Core, without the -NoExit switch to keep the process alive indefinitely), the PowerShell process exits when the script terminates.


Use of -NoExit would be a stopgap at best, because it would keep the PowerShell process around indefinitely, even though you presumably want it to live only for as long as the Properties dialog window is open - whenever the user chooses to close it.

Therefore, you need to synchronously wait for (a) the Properties dialog window to open and then (b) wait for it close before exiting the script.

You can do this with the help of the .NET UI Automation library as follows; note that the code uses PowerShell v5+ syntax:

using namespace System.Windows.Automation

# Load the UI Automation client assemblies.
# Requires Windows PowerShell or PowerShell Core v7+ (on Windows only).
Add-Type -AssemblyName UIAutomationClient; Add-Type -AssemblyName UIAutomationTypes

# Initiate display of the Windows folder's Properties dialog.
$App = New-Object -ComObject Shell.Application
$AppNS = $App.NameSpace('c:\windows')
$AppNS.Self.InvokeVerb('Properties')

# Comment out this line to suppress the verbose messages.
$VerbosePreference = 'Continue'

Write-Verbose 'Wating for the window''s creation...'
do {
  # Search among the current process' top-level windows for a winow
  # with class name '#32770', which is what the Properties dialog windows
  # use (don't know why, but it has been stable over time).
  $w = [AutomationElement]::RootElement.FindFirst([TreeScope]::Children, 
    [AndCondition]::new(
      [PropertyCondition]::new([AutomationElement]::ClassNameProperty, '#32770'),
      [PropertyCondition]::new([AutomationElement]::ProcessIdProperty, $PID)
    )
  )
  Start-Sleep -Milliseconds 100
} while (-not $w)

Write-Verbose 'Window has appeared, waiting for it to close...'

while ($w.Current.ProcessId) {
  Start-Sleep -Milliseconds 100
}

Write-Verbose 'Window is now closed, moving on.'

# At this point, if the script was invoked via PowerShell's CLI (powershell.exe -file ...)
# the PowerShell process terminates.

Now, invoking your PowerShell script as follows from your batch file will pop up the Properties dialog and wait for it to close before continuing:

@echo off

::  # ... your batch file

::  # Pop up the Properties dialog and *wait for it to close*.
powershell.exe -file script.ps1

::  # ...

If, by contrast, you simply want to launch the Properties dialog while continuing to run your batch file (be sure to disable the verbose messages first):

:: # Only *initiate* display of the Properties dialog and *continue execution*.
start /B powershell.exe -file script.ps1

这篇关于从 CMD/CLI 调用时 Powershell 脚本的奇怪行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆