ファイルが無ければ展開して実行するJScript
UWSC公式掲示板で、exeに画像が埋め込めれば良いのに、というのを見た。
興味が沸いたので、解決策を考えてみた。
思想としては、
- ファイルがなければ、自ファイルから展開する
- 展開したファイルを実行する
まずは解決策
例えば後で掲載するスクリプトを、CreateRunMaker.jsというファイル名で保存したなら、コマンドプロンプト等で、
cscript //nologo CreateRunMaker.js a.js b b.exe c.bmp
といった感じで実行する。
引数の「a.js b b.exe c.bmp」は、
No | 例 | 意味 |
---|---|---|
1 | a.js | 出力ファイル名 |
2 | b | 実行コマンド |
3 | b.exe | No.3以降は出力ファイルに付与するファイル名 |
4 | c.bmp | 同上 |
b.exe / c.bmp は、見つかるところにないとエラーになります。(同一フォルダが基本)
両方がちゃんとあると、a.js が作成されます。
その a.js を実行すると、b.exe / c.bmp がなければ作成し、「b」(すなわちb.exe)を実行します。
以下がCreateRunMaker.js
WScript.Quit((function() { // 出力ファイル名、コマンドとファイルを受け取る if(WScript.Arguments.length < 3) { WScript.Echo("args : (out path) (cmd) (file) [(file) ...]"); return 1; } // 実行用スクリプト var script = "WScript.Quit((function(){var d=WScript.CreateObject('Microsoft.XMLDOM').createElement('tmp');d.dataType='bin.hex';var e=WScript.CreateObject('ADODB.Stream');e.Open();e.Charset='Shift-JIS';e.Type=1;var f=function(b){d.nodeTypedValue=b;var a=d.text;var c=0,m=a.length;for(var i=0;i<m;i+=2){c*=256;c+=parseInt(a.substr(i,2),16)};return c};var g=function(){offset-=4;k.Position=offset;return f(k.Read(4))};var h=function(a){offset-=a;k.Position=offset;e.Position=0;e.SetEOS();e.Type=1;e.Write(k.Read(a));e.Position=0;e.Type=2;return e.ReadText()};var j=function(a,b){offset-=b;k.Position=offset;e.Position=0;e.SetEOS();e.Type=1;e.Write(k.Read(b));e.SaveToFile(a,2)};var k=WScript.CreateObject('ADODB.Stream');k.Open();k.Charset='iso-8859-1';k.Type=1;k.LoadFromFile(WScript.ScriptFullName);var l=k.Size,offset=l;var n=g();if(n!=0x6a756e6a){WScript.Echo('Script format error! :mark');return 1};n=g();if(n>offset){WScript.Echo('Script format error! :cmd len='+n);return 2};var o=h(n);var p=g();if(p>l){WScript.Echo('Script format error! :allNum='+p);return 3};var q=WScript.CreateObject('Scripting.FileSystemObject');for(var i=0;i<p;i++){var r=g();var s=h(r);var t=g();if(q.FileExists(s)){offset-=t}else{j(s,t)}};k.Close();k=null;q=null;e.Close();e=null;d=null;return WScript.CreateObject('WScript.Shell').run(o,1,false)})());"; // バイト配列操作用オブジェクト var binMan = WScript.CreateObject('Microsoft.XMLDOM').createElement('tmp'); binMan.dataType = "bin.hex"; // 文字列変換用オブジェクト var strMan = WScript.CreateObject('ADODB.Stream'); strMan.Open(); strMan.Charset = "Shift-JIS"; strMan.Type = 1; // Binary // 数値をバイト配列化して書き込む関数(BigEndian) var writeInt = function(i) { binMan.text = ("0000000" + i.toString(16)).slice(-8); outs.Write(binMan.nodeTypedValue); } // 文字列書き込みする関数 var writeStr = function(str, bNum) { strMan.Position = 0; strMan.SetEOS(); strMan.Type = 2; // Text strMan.WriteText(str); strMan.Position = 0; strMan.Type = 1; // Binary outs.Write(strMan.Read()); if(bNum) writeInt(strMan.Size); } // ファイル書き込みする関数 var writeFile = function(path) { strMan.Position = 0; strMan.SetEOS(); strMan.Type = 1; // Binary strMan.LoadFromFile(path); outs.Write(strMan.Read()); writeInt(strMan.Size); writeStr(path, true); } // スクリプトを出力する var outs = WScript.CreateObject('ADODB.Stream'); outs.Open(); outs.Charset = "iso-8859-1"; outs.Type = 1; // Binary writeStr(script, false); // 改行と0出力 writeInt(0x0d0a0000); // 指定ファイルを出力 // データ部を出力、ファイル名を出力、サイズと長さを出力 var allNum = 0; for(var i = 2; i < WScript.Arguments.length; i++) { writeFile(WScript.Arguments.item(i)); allNum++; } // 全体数を出力 writeInt(allNum); // コマンドを出力 writeStr(WScript.Arguments.item(1), true); // 'junj'マーク出力 writeStr('junj', false); outs.SaveToFile(WScript.Arguments.item(0), 2); // adSaveCreateOverWrite outs.Close(); outs = null; strMan.Close(); strMan = null; binMan = null; return 0; })());
備考
wshは後ろにごみがあっても気にしません。
それを利用して、バイナリファイルをくっつけてます。
ウイルス的にexeに憑依することも考えたけど、Generatorを作るのが面倒だったのでやめた。
サブフォルダ対応は、面倒だったのでやってません。
展開側でフォルダがなければ作るとかしないといけない気がして、、、。
あと、あんまり大きなファイルはだめです。ギガ単位とかは許して。
(理由は、一旦メモリーに展開しちゃうのと、添付のフォーマットのせい)
今回得た知識は、、、ADODB.Streamで扱うByte():バイト配列型は、
MSXMLが使えると、あっさり扱える、ということ。
これを使えば、文字コードを気にしながらバイナリーを扱うといった、
変なことをしないで済みます。
なお、ADODB.Streamで扱える文字コードは、
HKEY_CLASSES_ROOT\MIME\Database\Charset
にあるものの模様。
出力されるスクリプト本体は、以下のようなもの。
WScript.Quit((function() { // バイト配列操作用オブジェクト var binMan = WScript.CreateObject('Microsoft.XMLDOM').createElement('tmp'); binMan.dataType = 'bin.hex'; // 文字列変換用オブジェクト var strMan = WScript.CreateObject('ADODB.Stream'); strMan.Open(); strMan.Charset = 'Shift-JIS'; strMan.Type = 1; // Binary // バイト配列を数値化する関数(BigEndian) var b2i = function(b) { binMan.nodeTypedValue = b; var strb = binMan.text; var ret = 0, m = strb.length; for(var i = 0; i < m; i += 2) { ret *= 256; ret += parseInt(strb.substr(i, 2), 16); } return ret; } // offsetを4つずらし数値読み込みする関数 var readInt = function() { offset -= 4; ins.Position = offset; return b2i(ins.Read(4)); } // offsetを指定文字ずらし文字列読み込みする関数 var readStr = function(num) { offset -= num; ins.Position = offset; strMan.Position = 0; strMan.SetEOS(); strMan.Type = 1; // Binary strMan.Write(ins.Read(num)); strMan.Position = 0; strMan.Type = 2; // Text return strMan.ReadText(); } // offsetを指定文字ずらしファイル保存する関数 var saveBin = function(path, size) { offset -= size; ins.Position = offset; strMan.Position = 0; strMan.SetEOS(); strMan.Type = 1; // Binary strMan.Write(ins.Read(size)); strMan.SaveToFile(path, 2); // adSaveCreateOverWrite } // 自ファイルを開き、後ろから読み、コマンドとファイル数を得る var ins = WScript.CreateObject('ADODB.Stream'); ins.Open(); ins.Charset = 'iso-8859-1'; ins.Type = 1; // Binary ins.LoadFromFile(WScript.ScriptFullName); // 後ろは「junj」であること var size = ins.Size, offset = size; var n = readInt(); if(n != 0x6a756e6a) { WScript.Echo('Script format error! :mark'); return 1; } // コマンド長さを取得 n = readInt(); if(n > offset) { WScript.Echo('Script format error! :cmd len=' + n); return 2; } // コマンドを取得 var cmd = readStr(n); // 全体数を取得 var allNum = readInt(); if(allNum > size) { WScript.Echo('Script format error! :allNum=' + allNum); return 3; } // ファイル数分ループし、指定ファイルが存在するか確認する // あれば何もしない。なければ展開 var fso = WScript.CreateObject('Scripting.FileSystemObject'); for(var i = 0; i < allNum; i++) { var pathLen = readInt(); var path = readStr(pathLen); var fileSize = readInt(); if(fso.FileExists(path)) { offset -= fileSize; } else { saveBin(path, fileSize); } } ins.Close(); ins = null; fso = null; strMan.Close(); strMan = null; binMan = null; // ファイルチェックが終わったら、コマンドを実行する return WScript.CreateObject('WScript.Shell').run(cmd, 1, false); })());
/packer/で圧縮してちょっと加工してます。
こういうインストーラーってあんまり見ないですよね。
一番近いのは、自己解凍形式の圧縮ファイルの自動実行利用ですけど、
それだと毎回ファイルを出力することになる。
これだと、いつも同じファイルを実行して、なければインストール、あればそのまま実行、ってことができます。
expand とか makecab を使って、cab圧縮したものを操作した方が良かった気がする。
メモリー不足対策にもなったような、、、。
作成側は、スクリプトファイルとcab圧縮とコマンドやファイルリスト等をcopy /b連結。
実行側は、ファイルチェックと個別指定で展開。
、、、が、忘れることにする。
また、実行するコマンドでスクリプトを指定して、プロセス完了待ち後ファイル削除するようにすれば、実行中にしかファイルを存在しないようにできますが、、、それは、自己解凍形式圧縮ファイルの自動実行でやればいっか。
バグがあっても知りません(無責任!)