WindowsのERRORLEVELにハマった

 以下のようなコードを含んだバッチファイルが期待どおりに動かなくて悩んだ。

DIR /0 2>NUL

IF %ERRORLEVEL% NEQ 0 (ECHO Error) ELSE (ECHO Ok)

 DIR /0は必ずエラーになりERRORLEVELが1になり"Error"と出力されるはずであるが、テストしていたコマンドプロンプトでこのバッチファイルを実行するとはなぜか必ず"Ok"と出力されてしまう。

色々ネットを徘徊するとERRORLEVELという環境変数が上書きで設定されていて、本来のERRORLEVELが見えなくなっていることがわかった。コンソールで色々コマンドをテストしていたときに"SET ERRORLEVEL=0"としていたので、それがずっとが悪さをしていた。

テストしていたコマンドプロンプトで"SET ERRORLEVEL="してから、バッチファイルを実行したら正常に動いた。

SET コマンドのヘルプ より

コマンド拡張機能が有効な場合、SET によって表示される変数の一覧には
現れないいくつかの動的な環境変数があります。
これらの変数の値は、変数の値が展開されるときに
動的に計算されます。
ユーザーがこれらの名前の変数を明示的に定義する場合、
その定義は下記の動的な定義を無効にします。

%CD% - 現在のディレクトリ文字列に展開します。

%DATE% - DATE コマンドと同じフォーマットで現在の日付に展開します。

%TIME% - TIME コマンドと同じフォーマットで現在の時刻に展開します。

%RANDOM% - 0 から 32767 の間の任意の 10 進数に展開します。

%ERRORLEVEL% - 現在の ERRORLEVEL の値に展開します。

%CMDEXTVERSION% - 現在のコマンド プロセッサ拡張機能のバージョン番号に
展開します。

%CMDCMDLINE% - コマンド プロセッサを起動したオリジナル コマンド ライン
に展開します。

%HIGHESTNUMANODENUMBER%
- このコンピューター上の最大の NUMA ノード番号に展開します。

Windowsのバッチファイルやコマンドプロンプトでは、これらの特殊な環境変数はSETで変更してはいけない。

過去に書いた記事(batファイルで多重起動(二重起動)を防止する )のコードも修正しました。

 

参考にしたページ

終了コード errorlevel の考え方について -こんばんは。OSはWindowsXP - その他(プログラミング・Web制作) | 教えて!goo

ERRORLEVELは、一般の環境変数と異なり、SET で値を代入してはいけません。(参考: set /? で表示される最終ページ)
SET を使うと一般の環境変数となってしまい、次にSETするまで値は変わりません。コマンドの実行結果と切り離されると言うことです。

ERRORLEVELについてのメモ (1) - とあるソフトウェア開発者のブログ

ERRORLEVELについてのメモ (2) - とあるソフトウェア開発者のブログ

 

X-Frame-Optionsによるページ内フレームの制限を回避して一定間隔でリロードするBookmarklet

はじめに書いておきますが、このページはX-Frame-Options制限を回避してクリックジャッキングを可能にするものではありません。X-Frame-Options制限がかかったページでBookmarkletからフレームを操作して一定間隔でページのリロードを行うものです。
また、ここで紹介するリロードするBookmarkletはリロードに時間がかるので同様の機能を持ったアドオンの利用をおすすめします。
試行錯誤の末、以下のような奇っ怪なBookmarkletになりました。1時間の間、1分おきにリロードします。おそらくFirefoxでしか動きません。

javascript:(function(){I=60,T=3600,M=T/I;D=document;D.write('<html><script>C='+M+';X=new%20XMLHttpRequest();X.onreadystatechange=function(){if(X.readyState==4&&(X.status==200||X.status==304)){G=frames[1].document;G.open();G.write(X.responseText);G.close()};};function%20R(){X.open("GET",location,false);X.send();X.abort();if(C<=0)clearInterval(T);F=frames[0].document;F.open();F.write("<html><body%20style=\\"margin:0;\\">Auto%20reload%20at%20'+I+'%20seconds%20interval.%20"+(C--)+"%20times%20left.</body></html>");F.close();};T=setInterval("R()",'+I*1000+');setTimeout("R()",50);</script><frameset%20rows="20,*"><frame><frame></frameset></html>');D.close();})();

 


ページの文字コードUTF-8でない場合は文字化けするので以下ようにしてみて下さい。

javascript:(function(){I=60,T=3600,M=T/I;D=document;D.write('<html><script>C='+M+';X=new%20XMLHttpRequest();X.overrideMimeType("text/html;%20charset='+document.characterSet+'");X.onreadystatechange=function(){if(X.readyState==4&&(X.status==200||X.status==304)){G=frames[1].document;G.open();G.write(X.responseText);G.close()};};function%20R(){X.open("GET",location,false);X.send();X.abort();if(C<=0)clearInterval(T);F=frames[0].document;F.open();F.write("<html><body%20style=\\"margin:0;\\">Auto%20reload%20at%20'+I+'%20seconds%20interval.%20"+(C--)+"%20times%20left.</body></html>");F.close();};T=setInterval("R()",'+I*1000+');setTimeout("R()",50);</script><frameset%20rows="20,*"><frame><frame></frameset></html>');D.close();})();

以上、あくまでお遊び&実験です。実用にはなりません。



現在のページを1時間の間、一定間隔でリロードするBookmarklet

それだけですが、実はこれをBookmarkletでやろうとするとちょっと難しいのです。リロードさせること自体は簡単ですが、ページをリロードするとそのページで動いているBookmarkletスクリプトが止まってしまうため、2回目以降のリロードができません。なのでフレームを作って行うことになります。完成したBookmarkletは以下のようになります。1時間の間、1分おきにリロードします。おそらくFirefoxでしか動きません。

javascript:(function(){I=60,T=3600,M=T/I;document.write('<html><script>C='+M+';function%20R(){if(C=='+M+'){frames[1].location="'+location+'"}else{frames[1].location.reload()}if(C<=0)clearInterval(T);var%20D=frames[0].document;D.write("<html><body%20style=\\"margin:0;\\">Auto%20reload%20at%20'+I+'%20seconds%20interval.%20"+(C--)+"%20times%20left.</body></html>");D.close();};setTimeout("R()",50);T=setInterval("R()",'+I*1000+');</script><frameset%20rows="20,*"><frame><frame></frameset></html>');document.close();})();

 


ページが更新されないときは frames[1].location.reload(true) としてみて下さい。なお、スクリプトでフレームを操作している関係上、X-Frame-Options制限がかかったページはまったく表示ができません。同様の機能を持ったアドオンがあるのでそちらの利用をおすすめします。

Shift_JISでエンコードされたウェブページをUTF-8に変換して開くbookmarklet

はじめに

それ何が嬉しいの?なんでそんな無駄なことをするんだ?と言われそうなネタですが、FirefoxMozilla Archive Format(MAFF)アドオンでShift_JISなページを保存するとiframe内の文字が化けるので作ってみました。本当にニッチですね。MAFFアドオンを直せればいいんですが、それはまたの機会に。

同然ですが、Firefox専用です。

 

どうやって文字コードを変換するか

javascriptで文字コード変換 - Qiita

はじめは、上記のページで紹介されているようなライブラリを使って変換してdocument.write()しようかと考えていましたが、XMLHttpRequestだけでなんとかなったので使わずに済みました。具体的にはXMLHttpRequestのoverrideMimeTypeメソッドを使って文字コードを指定するとUTF-8に変換できました。正しく変換できたかはalert()で確認しました。

 

変換できたがdocument.write()すると文字化けけする

ところが変換した文字をdocument.write()しようとすると何故か文字化けします。どうも、ページのエンコードShift_JISのままだからのようですが、Firefoxのdocument.characterSetはリードオンリーで変更できません。どうにもならないので新しくウィンドウ(タブ)を開き、そこに書き出しました。

 

完成したBookmarklet

以下のようになりました。

 

javascript:(function(){D=document;W=window.open("","").document;X=new XMLHttpRequest();X.overrideMimeType("text/html;charset="+D.characterSet);X.onreadystatechange=function(){if(X.readyState==4&&(X.status==200||X.status==304)){W.write(X.responseText);W.close()};};X.open('GET',location,false);X.send();X.abort();})();

 

最初にdocument;D.write()を使ってスクリプト自体を書き出しているのはオマジナイです。これをしないと何故かoverrideMimeTypeを設定しても正しく変換できませんでした。

オマジナイは 不要でした。

 

batファイルで多重起動(二重起動)を防止する

バッチファイルで多重起動(二重起動)を抑止するコードを探してみました。

bat 多重|二重 実行|起動 抑止|禁止|防ぐ|防止|できないように|させない - Google 検索

 方法として、大まかに2つあることがわかりました。

  • 方法1:バッチファイル自身を追記モードで開く。
  • 方法2:ロックファイルを使う。
  • 方法3:tsklistコマンドを使って実行している数をチェックする。

たが、方法3はチェックする前に複数起動されるとどれもチェックに引っかかり、行いたい処理が実行できない。よって方法1をベースにブラッシュアップしてみた。

@echo off
call :set_errorlevel 1
call :main %* 3>>"%~dpnx0"
if %ERRORLEVEL% neq 0 echo Error 1>&2
goto :eof
:set_errorlevel
exit /b %1
:main
echo main start
timeout 15
echo main end
exit /b 0

ちなみに方法1には、実行中はバッチファイル自体を変更できないという特徴があり、これもメリットでもあるが注意が必要である。不用意にエディタで開いているとすでに実行中であると判断されてしまいます。また、利用者にバッチファイル自体に書き込める権限があるのは危険です。
これを回避するには、方法2でかつ、バッチファイル自体を読み取り専用にします。コードは以下のようになります。テスト中はattrib行をコメントアウトして下さい。

@echo off
attrib +r %~dpnx0
call :set_errorlevel 1
call :main %* 3> "%TEMP%%~nx0.lock"
if %ERRORLEVEL% neq 0 echo Error 1>&2
del "%TEMP%%~nx0.lock" >nul 2>&1
goto :eof
:set_errorlevel
exit /b %1
:main
echo main start
timeout 15
echo main end
exit /b 0

 

参考にしたページ

Microsoft製品) コマンドプロンプト:バッチの多重起動を抑制する - YiaoWang

バッチファイルlを2重起動できないようにしたい - その他(プログラミング) 締切済み 【OKWAVE】

バッチファイルの二重起動を防止する。(その2) Windows Script Programming

バッチの二重起動防止 - ひらいて

以上です。

 

2017/9/1 追記

コードに問題があったので修正しました。ERRORLEVELをSETで変更していたところをサブルーチンを使った方法に変更、:mainサブルーチンの戻り値を0するところを追加、リダイレクト元のFDが0だとtimeoutコマンドがエラーになるので3に変更しました。FD3を使う場合はそれ以上に変更して下さい。

前日のGMT日付が含まれたURLのページを表示するBookmarklet

Javascriptタイムゾーンを変更することができないのでちょっと厄介でした。

javascript:(function(d=new Date()){d=d.getTime()+(d.getTimezoneOffset()*60000);d=new Date(d+3600000*0);d.setDate(d.getDate()-1);document.location=`https://example.com/date=${d.getFullYear()}-${d.getMonth()+1}-${d.getDate()}`})();


参考にしたページ
【Javascript】翌日、前日の日付を取得する。- うぇぶろぐらまーな日誌
JavaScriptで違うタイムゾーンの時刻を得る - 刺身☆ブーメランのはてなダイアリー
JavaScript(jQuery)における文字列中に変数を記述する方法ってあるでしょうか? - スタック・オーバーフロー

ブックマークレットにdata:text/htmlを使ってiframeを含む内容を記述するとブロックされる時がある。

Firefoxブックマークレットにdata:text/htmlを使ってiframeを含む内容を記述すると、iframeが表示されない時がある。
正確にはhttpsのページを開いている状態で、そういったブックマークレットを実行すると起きる。ちなみにChromeでは起きない。
これが起きるとブラウザのコンソールに「混在アクティブコンテンツ “http://…” の読み込みをブロックしました」と警告が出る。
これを回避するには、URL欄の左のiマークをクリックして「このセッションのみ保護を無効にする」をクリックすれば良いが、いちいち面倒である。
そこでこれを回避する方法を探ってみた。最初にjavascriptのlocationで転送してみた。

data:text/html,<script>location='data:text/html,<html><body><iframe...

しかし同じくブロックされた。次にhtmlのmeta refreshを使ってみた。

data:text/html,<html><head><meta http-equiv='refresh content='0;URL=data:text/html,<html><body><iframe...

なぜかよくわからないがブロックされず、無事解決した。