PowerShell起動時にHiddenを指定しても一瞬ウィンドウが表示されてしまう
はじめに
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.vbs
が b.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
It flashes the powershell window briefly.
About PowerShell.exe
-WindowStyle <Window style>
Sets the window style for the session. Valid values are Normal, Minimized, Maximized and Hidden.
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?
Discussion