読者です 読者をやめる 読者になる 読者になる

XORveR.com の日記

XORveR.com の公式ブログです。

ニッチ過ぎて誰からも「いいね」されなかったシリーズ2 (wsf編その1)

まえがき

シリーズ第一弾は意外と好評だったので、第二弾を早々に書きます。

wsf!!!

さて、wsfって使われていないように見られますが・・・意外なことに物凄くデキる子なんですよ。
Windows でテキスト処理その他と言われたら、何を提案しますか?
java? c#? いえいえ perl も ruby も差し置いて、私は wscript (WSH) を推します。
というか、アソコの企業でもアチラの企業でも、私の書いた wsf は今も使われています。(はず)
五年も前に書いたコードのことを聞いたら、まだ現役と言われましたし。

wsf編その1では、お馴染みの DosPromptHere を wsf で書いたものを披露します。
DosPromptHere ならば実用品で何を行うモノなのかが明確ですので、サンプルとして手頃でしょう。

wsf編『その1』?

今回のサンプルは『基本形』です。

まず、今回はサブルーチンのライブラリを使っていません。
ライブラリ化は単体テストを書くためにも応用形としては外せませんね。
ただ、そこらはサラっと流す予定です。

多態性の利用もスクリプトなんですから、それもパス。

それより何より、wsfの恐るべき機能(というかXMLであること)を利用していないからです。
それについては後日までの隠し玉です。フフフ

技術としての注目点

本サンプルでは、以下の点が目新しい技術だろうと思っています。

  1. WHS の job による処理の分離(インストール/アンインストール部と実行処理部)
  2. レジストリを使うため、勝手に再実行して UAC を受ける方法(runas)

総合的にアプリとして使える枠組みこそがサンプルとしての意義です。
VBS から ActiveX を叩くなんてのは、全然目新しくないですよね?

処理の分離と、レジストリ操作の手順としての UAC 通過手順は、wsf の基本です。
ただでさえ UAC は一手間ですから、管理者として実行なんてのは自動で出来ないと面倒です。

wsf編

wsf?なんぞそれ?

という方にはまずは、ここの紹介でしょうか。
Windows スクリプト ファイル (.wsf) を使用する

そして
technet.microsoft.com
そして
technet.microsoft.com



なのですが   今ではPowerShellに押されて見る影もない状態ですね。最早いらない子?Script56.chmも入手困難ですし…

サンプルのソースコード

このソースコードを DosPrompt.wsf として任意の場所に置きます。

<?xml version='1.0' encoding='utf-8' ?>
<package>
    <job id="DosPrompt">
        <runtime>
            <description>PowerToys の Open Command Window Here に Doskey を自動実行する機能を追加する。</description>
            <named
                name="uninstall" type="boolean" required="false"
                helpstring="これを設定すると、コンテキストメニューを削除します。"
            />
        </runtime>
        <object id="Shell" progid="WScript.Shell"/>
        <object id="ShellApp" progid="Shell.Application"/>
        <object id="fso" progid="Scripting.FileSystemObject"/>

        <script language="VBScript">
<![CDATA[
            Option Explicit

            Const toolName = "DosPrompt ツール"

            ' スクリプトの情報
            Dim ScriptName: ScriptName = WScript.ScriptFullName
            Dim ScriptPath: ScriptPath = fso.GetParentFolderName(ScriptName)

            ' Vista 以降判定
            Dim postVista:  postVista = IsPostVista

            ' 引数
            Dim uac
            Dim unInstall
            uac = WScript.Arguments.Named.Exists("uac")
            unInstall = WScript.Arguments.Named.Exists("uninstall")
            if (Not uac) And postVista Then
                Dim arg
                If unInstall Then
                    arg = """" & ScriptName & """ /uac /uninstall"
                Else
                    arg = """" & ScriptName & """ /uac"
                End If
                ' Vista 以降ならば、管理者として再実行 (レジストリ操作の都合)
                ShellApp.ShellExecute WScript.FullName, arg, "", "runas", 1
                WScript.Quit 0
            End If

            ' doskeyマクロ定義ファイル
            Dim macrofile:  macrofile = fso.BuildPath(ScriptPath, "doskey.config.txt")

            ' 登録
            Dim command
            Const TopKey = "HKCR\Folder\shell\DosPromptHere\"
            Const TopKeyRunAs = "HKCR\Folder\shell\DosPromptAdmin\"

            If unInstall Then
                ' ===== UnInstall =====
                MsgBox toolName & " 関連のコンテキストメニューを抹消します。" _
                    & vbLf & "スクリプト自身、マクロ定義ファイルは自動では削除されません。" _
                    & vbLf & "マクロ定義ファイル : " & macrofile, , WScript.ScriptName

                ' ログインユーザとして起動するcmd
                Shell.RegDelete TopKey & "Command\"
                Shell.RegDelete TopKey

                If postVista Then
                    ' ----- Vista 以降
                    ' 管理者として起動するcmd
                    Shell.RegDelete TopKeyRunAs & "Command\"
                    Shell.RegDelete TopKeyRunAs
                End If

                ' アンインストール用のショートカット
                Dim lnkPath
                lnkPath = fso.BuildPath(ScriptPath, toolName & "のアンインストール.lnk")
                If fso.FileExists(lnkPath) Then fso.DeleteFile lnkPath, true
            Else
                ' ===== Install =====
                MsgBox toolName & " 関連のコンテキストメニューを登録します。" _
                    & vbLF & "抹消時は /uninstall を付けて起動してください。" _
                    & vbLf & "(ツールのフォルダにアンインストール用ショートカットを用意してあります)" _
                    & vbLf & "ツールのフォルダ : " & ScriptPath, , WScript.ScriptName

                ' デフォルトの DosKey マクロ定義ファイルの準備
                If Not fso.FileExists(macrofile) Then
                    With fso.CreateTextFile(macrofile)
                        .WriteLine "[cmd.exe]"
                        .WriteLine "config=""" & macrofile & """"
                        .WriteLine "newconfig=doskey /macrofile=""" & macrofile & """"
                        .WriteLine "macros=doskey /macros"
                        .Close
                    End With
                End If

                ' ログインユーザとして起動するcmd
                command = "wscript """ & ScriptName & """ //JOB:Run /runas:false /path:""%1"" /macrofile:""" & macrofile & """"
                Shell.RegWrite TopKey, "DosPrompt", "REG_SZ"
                Shell.RegWrite TopKey & "Command\", command, "REG_SZ"

                If postVista Then
                    ' ----- Vista 以降
                    ' 管理者として起動するcmd
                    command = "wscript """ & ScriptName & """ //JOB:Run /runas:true /path:""%1"" /macrofile:""" & macrofile & """"
                    Shell.RegWrite TopKeyRunAs, "DosPrompt (Admin)", "REG_SZ"
                    Shell.RegWrite TopKeyRunAs & "Command\", command, "REG_SZ"
                End If

                ' アンインストール用のショートカット
                CreateJobShortcut _
                    ScriptPath _
                ,   "DosPrompt" _
                ,   toolName & "のアンインストール" _
                ,   WScript.ScriptName & "をアンインストールします。" _
                ,   "/uninstall"
            End If

            WScript.Quit

        ' --------------------------------------------------------------------------------
        ' Vista 以降の判定
        Function IsPostVista
            With GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\.\root\cimv2")
                Dim os
                For Each os in .ExecQuery("SELECT * FROM Win32_OperatingSystem")
                    If left(os.Version, 3) >= 6.0 Then
                        IsPostVista = True
                        Exit Function
                    End If
                Next
            End With
            IsPostVista = False
        End Function

        ' --------------------------------------------------------------------------------
        ' このスクリプト内のJOBに対するショートカットを新規作成
        Sub CreateJobShortcut(lnkPath, id, name, description, arg)
            Dim Shortcut, lnkFilePath
            lnkFilePath = fso.BuildPath(lnkPath, name & ".lnk")
            If fso.FileExists(lnkFilePath) Then fso.DeleteFile lnkFilePath, true
            With Shell.CreateShortcut(lnkFilePath)
                .Description = description
'               .IconLocation = fso.BuildPath(ScriptPath, "DosPrompt.ico")
                .IconLocation = WScript.Path & "\wscript.exe,2"
                .TargetPath = WScript.Path & "\wscript.exe"
                .Arguments = """" & ScriptName & """ //JOB:" & id & " " & arg
                .WindowStyle = 1            ' 通常のウィンドウ
                .WorkingDirectory = ScriptPath
                .Save
            End With
        End Sub
]]>
        </script>
    </job>

    <job id="Run">
        <runtime>
            <description>指定したフォルダにおいて cmd.exe を管理者/一般ユーザで起動する。Doskey 自動実行つき。</description>
            <named
                name="runas" type="boolean" required="false"
                helpstring="これに true を指定すると、管理者として cmd.exe を起動します。"
            />
            <named
                name="path" type="string" required="true"
                helpstring="起動時のカレントディレクトリを指定します。"
            />
            <named
                name="macrofile" type="string" required="true"
                helpstring="DosKey で使用するマクロファイルを指定します。"
            />
        </runtime>
        <object id="Shell" progid="WScript.Shell"/>
        <object id="ShellApp" progid="Shell.Application"/>
        <object id="fso" progid="Scripting.FileSystemObject"/>
        <script language="VBScript">
<![CDATA[
            Option Explicit
            Dim args:   Set args = WScript.Arguments
            Dim runas: runas = args.Named.Item("runas")
            Dim path: path = args.Named.Item("path")
            Dim drive: drive = fso.GetDriveName(path)
            Dim macrofile: macrofile = args.Named.Item("macrofile")
            ' コマンド拡張機能・ファイル名(Ctrl+F)およびディレクトリ名補完文字(Ctrl+D)を有効にして cmd.exe を起動。
            ' 1. DosKey を既定のマクロファイルを適用して実行。
            ' 2. ドライブとフォルダを移動。
            ' 3. 既定のマクロファイルを編集するためのコマンドを表示。
            Dim cmdarg: cmdarg = "/E:ON /F:ON /K" _
                & " doskey /macrofile=""" & macrofile & """" _
                & " && " & drive & " && cd """ & path & """" _
                & " && " & "echo please enter ""config"""
            If runas Then
                ' 管理者として起動
                Dim MacroPath:  MacroPath = fso.GetParentFolderName(macrofile)
                Dim admincmd: admincmd = fso.BuildPath(MacroPath, "admin.cmd")
                If fso.FileExists(admincmd) Then
                    cmdarg = cmdarg & " && " & admincmd
                End If
                ShellApp.ShellExecute "cmd.exe", cmdarg, "", "runas", 1
            Else
                ' 一般ユーザとして起動
                Shell.Run "cmd.exe " & cmdarg
            End If
]]>
        </script>
    </job>

</package>

サンプルのインストール

インストールは、スクリプトをダブルクリックします。

DosPromptHere はレジストリ操作がありますので、インストール時にはUACが出ます。
まずは、ソースコードに目を通してから、あくまで自己責任でw

サンプルのアンインストール

アンインストールは、スクリプトと同じ場所に "DosPrompt ツールのアンインストール" というショートカットが生成されます。

実行

インストールすると、フォルダのコンテキストメニューに
f:id:XORveR:20161031150632p:plain
というように、DosPrompt(Admin) と DosPrompt が追加されます。
DosPrompt(Admin) は、管理者として実行します。

doskeyマクロ定義ファイル

インストールすると、DosPrompt.wsf と同じ場所に doskey.config.txt という名前でdoskeyマクロ定義ファイルが置かれます。
doskeyマクロ定義ファイルは、この DosPromptHere ツールの目玉機能です。
ぶっちゃけ、.bashrc でコマンドをエイリアス登録する感じです。

[cmd.exe]
config="C:\script\DosPrompt\doskey.config.txt"
newconfig=doskey /macrofile="C:\script\DosPrompt\doskey.config.txt"
macros=doskey /macros

コマンドプロンプト上で "config" というコマンドを叩けば、メモ帳か何かでテキストは開きます。
編集して保存したら、"newconfig" と叩きます。
そうすれば、そのコマンドプロンプト上でも反映されます。

マクロの何が嬉しいのか?

何よりも PATH に登録しないで、コマンドを実行できます。

例として go 言語関係のコマンドを追加してみます。
以下を追加し、コマンドプロンプト上で newconfig(==doskey /macrofile="C:\script\DosPrompt\doskey.config.txt") と叩きます。

go=d:\Go\bin\go.exe $*
godoc=d:\Go\bin\godoc.exe $*
gofmt=d:\Go\bin\gofmt.exe $*

すると、go コマンドを使えるようになります。

C:\script\DosPrompt>go version
go version go1.6.3 windows/amd64

コマンドのパスは、それぞれの環境での場所に変更してください。
go コマンドであることが重要ではないので注意。


解説は、そのうち!(ォィ
では!