PowerShell起動時にHiddenを指定しても一瞬ウィンドウが表示されてしまう

6 min read読了の目安(約5900字

はじめに

PowerShell 起動時のオプションは多数ありますが、その中で -WindowStyle Hidden があります。このオプションはウィンドウを非表示にするものですが、期待に反してウィンドウが一瞬表示されてしまいます。

利用シーンによっては一瞬表示されてもさほど困らないかもしれません。しかし、タスクスケジューラーで何度も実行するたびにpowershellのウィンドウが一瞬表示されるのは、あまり望ましくないことが多いでしょう。

この記事に書かれているのは何か?

  • なぜウィンドウが一瞬表示されるか
  • ウィンドウを表示させないためにはどうすればよいか

前提

PowerShell のバージョンは 5.1 です。

PS tmp> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.19041.610
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.19041.610
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

なぜウィンドウが一瞬表示されるか

答えはPowerShellのGitHubのIssueにあります。

powershell.exe is a console application. The console window is automatically created by the OS when the process starts. The powershell.exe code that processes -WindowStyle Hidden is therefore executed after the console window is opened hence the flash.

ざっと訳すと、powershell.exe はコンソールアプリケーションであり、コンソールアプリケーションはOSによってウィンドウが作られ、PowerShellのコードが -WindowStyle Hidden を処理するので一瞬表示されるため、ウィンドウが一瞬表示されるようです。

このIssueは、2017年に作られたものですが、執筆時点(2021-01-26)でも、修正されていません。

ウィンドウを表示させないためにはどうすればよいか

VBS等で実行する方法がある。VBSのRun()で外部コマンドを実行し、かつ、表示しない形にすれば一瞬表示されなくて済みます。

ここでは動作確認のため c:\a\a.vbs と渡された引数を表示する b.ps1 を作ります。まず、 a.vbs を作ります。

# c:\a ディレクトリを作る
New-Item -Type Directory c:\a

@'
Option Explicit

Function qq(str)
  qq = """" & str & """"
End Function

Function GetCmd()
  Dim args, parts(), index

  Set args = WScript.Arguments
  ReDim parts(args.Count)
  For index=0 To args.Count-1
    parts(index) = qq(args.Item(index))
  Next
  GetCmd = Join(parts, " ")
End Function

Dim ws, cmd
Set ws = WScript.CreateObject("WScript.Shell")
cmd = GetCmd
ws.Run cmd, 0
'@ | Set-Content c:\a\a.vbs

a.vbs を動作確認するための、 b.ps1 を作ります。

@'
[CmdletBinding()]
param([string[]][Parameter(ValueFromRemainingArguments)]$Arguments)
$text = ""
for ($i=0; $i -lt $Arguments.Count; $i++) {
    $text += "${i}: '$($Arguments[$i])'`r`n"
}
$text | Out-File c:\a\b.txt
'@ | Set-Content c:\a\b.ps1

b.ps1 の動作確認をします。ここでは4つの引数を指定します。

PS tmp> Set-Location c:\a
PS a>

PS a> .\b.ps1 str1 "str2" "str 3" "str`"4"

PS a> Get-Content b.txt
0: 'str1'
1: 'str2'
2: 'str 3'
3: 'str"4'

4つの引数はいずれも期待通りに出力されることがわかりました。期待通りです。

では、 a.vbs を実行してみます。

PS a> Remove-Item b.txt

PS a> .\a.vbs powershell.exe -File .\b.ps1 str1 "str2" "str 3" "str`"4"

PS a> Get-Content .\b.txt
0: 'str1'
1: 'str2'
2: 'str 3'
3: 'str4'

a.vbsb.ps1 を実行し、引数を渡すことができました。しかし、最後の引数は str"4" であるべきなのに str4 とダブルクォートが出力されませんでした。これは VB スクリプトの引数として、ダブルクォートを受け取ることができないことが理由です。この動作を避けてダブルクォートを引数で受け取りたい場合は、次のように VBスクリプト内で WMI を呼び出し、ダブルクォートを取得することはできます。

@'
Set objSWbemServices = GetObject("WinMgmts:Root\Cimv2")
Set colProcess = objSWbemServices.ExecQuery("Select * From Win32_Process")
For Each objProcess In colProcess
    If InStr(objProcess.CommandLine, WScript.ScriptName) <> 0 Then
        InputBox "", "", objProcess.CommandLine
        Exit For
    End If
Next
'@ | Set-Content c.vbs

.\c.vbs str1 "str2" "str 3" "str`"4"
  # --> "C:\WINDOWS\System32\WScript.exe" "C:\a\c.vbs" str1 str2 "str 3" str"4

ただ、Windows ではファイルパス中にダブルクォートを含めることができないなど、でダブルクォートを引数で受け取ることはあまりないと思います。そのため、本記事では、引数の中にスペースは許容し、ダブルクォートは想定しないものとして、進めます。

次に長い引数を与えるとどうなるでしょうか。引数の長さが 16384 文字の場合、次の通り、実行できるようでした。

PS a> [Math]::Pow(2,14)
16384

PS a> $longString = "x" * ([Math]::Pow(2,14))

PS a> .\a.vbs powershell.exe -File .\b.ps1 arg1 "$longString"

しかしながら、長すぎると実行に失敗するようです。例えば次のように長い引数を与えると、異常終了します。

PS a> [Math]::Pow(2,15)
32768

PS a> $longString = "x" * ([Math]::Pow(2,15))
PS a> $longString = "x" * 32685

PS a> .\a.vbs powershell.exe -File .\b.ps1 arg1 "$longString"
プログラム 'a.vbs' の実行に失敗しました: ファイル名または拡張子が長すぎます。
発生場所 行:1 文字:1
+ .\a.vbs powershell.exe -File .\b.ps1 arg1 "$longString"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~。
発生場所 行:1 文字:1
+ .\a.vbs powershell.exe -File .\b.ps1 arg1 "$longString"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (:) [], ApplicationFailed
   Exception
    + FullyQualifiedErrorId : NativeCommandFailed

長さによっては、VBスクリプトの制約で実行できないこともあります。

PS a> $longString = "x" * 32684

PS a> .\a.vbs powershell.exe -File .\b.ps1 arg1 "$longString"

この場合、次のダイアログが表示され、異常終了しました。

---------------------------
Windows Script Host
---------------------------
スクリプト:	C:\a\a.vbs
行:	22
文字:	1
エラー:	ファイル名または拡張子が長すぎます。
コード:	800700CE
ソース: 	(null)

---------------------------
OK
---------------------------

今回はある引数が長い場合のみを想定していますが、この例だけにとらわれず、引数の数や長さには上限があるものと想定しましょう。

まとめ

この記事では次の内容を紹介しました。

  • powershell.exe 起動時に -WindowStyle Hidden を指定してもウィンドウが一瞬表示される理由を紹介しました。
  • 一瞬表示されないようにするために VB スクリプトを利用する方法を紹介しました。

参考資料

Powershell -WindowStyle Hidden still shows a window briefly #3028

https://github.com/PowerShell/PowerShell/issues/3028#issuecomment-275212445

It flashes the powershell window briefly.

About PowerShell.exe

https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_powershell_exe?view=powershell-5.1#-windowstyle-window-style

-WindowStyle <Window style>
Sets the window style for the session. Valid values are Normal, Minimized, Maximized and Hidden.

Double quotes in VBScript argument

https://stackoverflow.com/questions/4192376/double-quotes-in-vbscript-argument

As I read and experienced for myself VBScript removes all double quotes from and argument. Does anyone know a way around this? How to pass double quotes into the script?