自動化ツール/UWSC

Last-modified: 2019-02-14 (木) 19:30:03

Mery用構文ファイル

Mery同梱の構文ファイルがちょっと使いにくかったので独自に作成したもの。

MSBuild用プロジェクトファイルのテンプレート

.uwsファイルをExe化する(Pro版)。

<?xml version="1.0" encoding="utf-8" ?>
<!--
  MSBuild用プロジェクトファイルの例
    同一フォルダにある.uwsファイルをExe化する
    このプロジェクトファイルは.uwsファイルと同じフォルダにあること
-->
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- Uws2Exe.exeのフルパスとオプションの定義 -->
  <PropertyGroup>
    <UWS2EXE>$(MSBuildProgramFiles32)\UWSC\Uws2Exe.exe</UWS2EXE>
    <OPTIONS>/ME</OPTIONS>
  </PropertyGroup>
  <!-- ソースファイル名 -->
  <ItemGroup>
    <SOURCE_FILES Include="foo.uws" />
    <!--
    <SOURCE_FILES Include="bar.uws" />
    <SOURCE_FILES Include="baz.uws" />
      :
    -->
  </ItemGroup>
  <!-- Buildターゲットからビルド後に呼び出されるターゲット -->
  <Target Name="Build" Inputs="@(SOURCE_FILES)" Outputs="@(SOURCE_FILES->'%(Filename).exe')">
    <Exec Command="%22$(UWS2EXE)%22 $(OPTIONS) %22%(SOURCE_FILES.Identity)%22" WorkingDirectory="$(MSBuildProjectDirectory)" />
  </Target>
</Project>

IExpressで実行形式ファイルを作る(UWSCフリー版)

※UWSC Free版の二次配布は認められていないので、あくまで個人的な利用に留められたい。

同梱サンプルスクリプトGruGru.uwsを実行する.exeファイルは以下の手順で作成可能。なお、Pro版で作成した.exeファイルと違い、細かい制限などあると思うが詳しくテストしていないので注意。*1

パッケージタイトル
sample*2
パッケージファイル(.exeファイル)名
GruGru.exe
.sedファイル名
GruGru.sed
  1. コマンドプロンプトや「ファイル名を指定して実行」などから iexpress.exe を実行しIExpressのウィザードを起動
  2. [Create new SelfExtraction Directive file.]を選択して[次へ]
  3. [Extract files and run an installation command]を選択して[次へ]
  4. パッケージタイトルに sample と入力して[次へ]
  5. [No prompt.]を選択して[次へ]
  6. [Do not display a license.]を選択して[次へ]
  7. [Add]をクリックし、ファイルオープンダイアログボックスから UWSC.exe とスクリプトファイル(.uws)など動作に必要なファイルを選択(複数選択可)し、全て登録し終えたら[次へ]
    • 今回の例では UWSC.exe とGruGru.uws を選択すれば動作する
  8. [Install Program]に UWSC.exe GruGru.uws と入力、[Post Install Command]は <None> のままにして[次へ]
    • スクリプトファイル名に半角空白が含まれる場合、スクリプトファイル名を半角ダブルクォーテーションで囲む必要がある
  9. [Default (recommanded)]を選択して[次へ]
  10. [No message.]を選択して[次へ]
  11. [Browse]をクリックしてファイルセーブダイアログボックスから GruGru.exe の保存先を入力、[Store files using Long File Name inside Package]をチェック(確認ダイアログボックスが開くので[はい]で閉じる)して[次へ]
    • スクリプトファイル名が8.3形式なら[Store files using Long File Name inside Package]のチェックは不要。8.3形式の意味が判らないならチェックしておけば間違いない
  12. [No restart]を選択して[次へ]
  13. 今までの設定を .sed ファイルに保存するなら[Save SelfExtraction Directive (SED) file:]を、保存しなくて良いなら[Don't save.]選択して[次へ]
    • デフォルトではパッケージファイルと同じフォルダに.sedファイルが作られる
  14. [次へ]でパッケージファイル生成
  15. [完了]で IExpress を終了

設定を .sed ファイルに保存した場合、次回からパッケージファイルの作成手順が短縮できる。

  1. コマンドプロンプトや「ファイル名を指定して実行」などから iexpress.exe を実行
  2. [Open existing SelfExtraction Directive file:]で .sed ファイルを選択して[次へ]
  3. [Create Package.]を選択して[次へ]
  4. [次へ]でパッケージファイル生成
  5. [完了]で IExpress を終了

またコマンドラインから iexpress /N /Q /M GruGru.sed と実行すれば直ちにパッケージファイルが作られる。

Call文で特殊変数を使う

Call GET_CUR_DIR + "\Lib.uws"
Call GET_CUR_DIR + "\Func.uws(<#DBL>Foo<#DBL>, 100)"  // リテラルなら引数も渡せる

ローカル変数/グローバル変数だとエラーだが特殊変数(ディレクトリ)なら使える。

配列を関数の戻り値にする

Dim ary = RtnArray()  // Dimを省略してもエラーにならない
For i = 0 To Length(ary) - 1
  Print ary[i]
Next
Function RtnArray()
  Result = SafeArray(0, 3 - 1)  // 空配列を返すには SafeArray(0, -1) とすれば Length() が 0 を返す
  Result[0] = "Aaa"
  Result[1] = "Bbb"
  Result[2] = "Ccc"
FEnd

引数で返すならVarを付けるだけ。

Procedure RtnArray(Var a[])
  a[0] = "aaa"
  a[1] = "bbb"
  a[2] = "ccc"
FEnd

配列を引数にする

// ↓どちらの書き方でも通る
Dim x[5][10]
Dim y[10, 5]
x[1][1] = 100
y[2, 2] = 50
Foo(x)
// Length()だとサイズが、Resize()だと上限値が返る;これで二次元配列の大きさがわかる
Procedure Foo(a[][])
  Print Length(a)  // 6
  Print Resize(a)  // 5
  Print Length(a[1])  // 11
  Print Resize(a[1])  // 10
FEnd

関連付けられたアプリケーションで開く

ファイルまたはショートカット(.lnk)を関連付けられたアプリケーションで開くには DosCmd("<#DBL>filepath<#DBL>") または PowerShell("<#DBL>filepath<#DBL>") を使う。

環境変数の取得

特定の環境変数を得るには ExpandEnvironmentStrings() メソッドか DosCmd("echo %environname%") を使う。

// My Documentsフォルダのパスを得る
objShell = CreateOleObj("WScript.Shell")
myDoc = objShell.ExpandEnvironmentStrings("%USERPROFILE%") + "\Documents"

全て得るなら DosCmd("set") かWScript.ShellオブジェクトのEnvironmentプロパティを使って戻り値を加工する。

// 全環境変数を連想配列にまとめる
HashTbl dic
GetEnvHash(dic)
For i = 0 To Length(dic) - 1
  Print dic[i, HASH_KEY] + " -> " + dic[i, HASH_VAL]
Next
// 連想配列は戻り値には使えないようだが引数なら渡せる
Procedure GetEnvHash(Var dic[])
  lines = Split(DosCmd("set"), "<#CR>", TRUE)
  For i = 0 To Length(lines) - 1
    l = BetweenStr(lines[i], "", "=")
    r = BetweenStr(lines[i], "=", "")
    dic[l] = r
  Next
FEnd
// Environmentプロパティ版:値に%windir%など変数名が混じっていて使いにくい
Procedure GetEnvHash2(Var dic[], type = "")
  objShell = CreateOLEObj("WScript.Shell")
  Select type
    Case "USER", "SYSTEM"
      objEnviron = objShell.Environment(type)
    Default
      objEnviron = objShell.Environment
  SelEnd
  For i In objEnviron
    l = BetweenStr(i, "", "=")
    r = BetweenStr(i, "=", "")
    dic[l] = r
  Next
FEnd

name="submit" となっているボタンを押す

name="submit" となっているボタンはIESetData()でSUBMITできないことがある模様。

IESetData(objIE, TRUE, "submit", "送信")  // これだと上手く動作しない場合がある
objIE.Document.Forms[n].Submit()  // これか
objIE.Document.Forms[n].Submit.Click()  // これのどちらかで対応
objIE.Document.Forms[n].Elements[m].Click()  // これでもOK

For文のカウンタ変数はループ中に変更しないほうが良い

For i = 0 To 5 - 1
  If i = 2 Then i = 3
  Print i	// 0, 1, 3, 3, 4 と出力される(0, 1, 3, 4 とはならない)
Next

Token()で改行を区切り文字にする

// クリップボードのテキストを改行で区切る(空行はスキップ)
// クリップボード中に <#CR> という文字列があると改行と見なされ区切られる
sep = Chr(13) + Chr(10)	// CR+LF
str = GetStr(0)
line = Token(sep, str, TRUE, TRUE)
While (Length(str) > 0)
	MsgBox("行: " + line)
	line = Token(sep, str, TRUE, TRUE)
WEnd

改行で区切るなら Split(GetStr(0), "<#CR>") で配列にしてしまう方が扱いやすい。

スクリプト自身の実行を一時停止する

Alt+F1(UWSCのデフォルト設定)で再開する。「読込み」で.uwsスクリプトを開き、「再生」で実行した場合のみ動作する模様(.exe版や関連付けで実行した.uwsスクリプトだとうまく動作しない)。

id = GetId(GET_THISUWSC_WIN)
MsgBox("一時停止します")
ScKey(id, VK_ALT, VK_F1)
MsgBox("一時停止しました")

レーベンシュタイン距離を求める

大文字小文字を比較する場合は Option SAMESTR を宣言する。

// https://nayose.net/blog/address/246/ をベタ移植
Function lsDist(baseText, tryText)
    Result = 0
    If baseText = tryText Then Exit
    bl = Length(baseText)
    tl = Length(tryText)
    If bl = 0 Then
        Result = tl
        Exit
    EndIf
    If tl = 0 Then
        Result = bl
        Exit
    EndIf
    Dim matrix[bl, tl]
    For i = 0 To bl
        matrix[i, 0] = i
    Next
    For j = 0 To tl
        matrix[0, j] = j
    Next
    For i = 1 To bl
        For j = 1 To tl
            If Copy(baseText, i, 1) = Copy(tryText, j, 1) Then
                cost = 0
            Else
                cost = 1
            EndIf
            Dim work[] = matrix[i-1, j] + 1, matrix[i, j-1] + 1, matrix[i-1, j-1] + cost
            matrix[i, j] = CalcArray(work, CALC_MIN)
        Next
    Next
    Result = matrix[bl, tl]
FEnd

フォーム内のエレメントのNameやIdをCSVファイルに出力

IEでフォームを操作する時に使う。*3

Const CsvPath = GET_CUR_DIR + "\TagElementList.csv"
fid = FOpen(CsvPath, F_WRITE)
If fid = -1 Then
    MsgBox(CsvPath + " をオープンできませんでした")
    ExitExit
EndIf
url = Input("URLを入力してください<#CR>(空白時、現在IEでアクティブなURL)")
If url = EMPTY Then ExitExit
Try
    If Length(url) < 1 Then
        objIE = GetActiveOLEObj("InternetExplorer.Application")
    Else
        objIE = CreateOLEObj("InternetExplorer.Application")
        objIE.Visible = TRUE
        objIE.Navigate(url)
        Repeat
            Sleep(0.5)
        Until !objIE.Busy And (objIE.ReadyState = 4)
    EndIf
Except
    MsgBox(TRY_ERRLINE + " 実行時エラー: " + TRY_ERRMSG + "が発生しました。中断します")
    ExitExit
EndTry
FPut(fid, "Name",  1, 1)
FPut(fid, "Id",    1, 2)
FPut(fid, "Type",  1, 3)
FPut(fid, "Name",  1, 4)
FPut(fid, "Id",    1, 5)
FPut(fid, "Value", 1, 6)
y = 2
With objIE.Document
    For i = 0 To .Forms.Length - 1
        FPut(fid, .Forms[i].Name, y, 1)
        FPut(fid, .Forms[i].Id,   y, 2)
        y = y + 1
        For j = 0 To .Forms[i].Elements.Length - 1
            FPut(fid, .Forms[i].Elements[j].Type,  y, 3)
            FPut(fid, .Forms[i].Elements[j].Name,  y, 4)
            FPut(fid, .Forms[i].Elements[j].Id,    y, 5)
            FPut(fid, .Forms[i].Elements[j].Value, y, 6)
            y = y + 1
        Next
    Next
EndWith
FClose(fid)
MsgBox("終了しました")

Excelワークシートに対してSQLで問い合わせ

1行目は見出し(列名)であること。

// Excelワークシートに対してSQLで問い合わせ(ADO)
// 結果はクリップボードにコピーする
// 要2007 Office system ドライバ: データ接続コンポーネント
// インストールされていない場合は https://www.microsoft.com/ja-jp/download/details.aspx?id=23734 からDL
//Const adOpenForwardOnly = 0
//Const adOpenKeyset = 1
//Const adLockReadOnly = 1
Const adUseClient = 3
Const DefaultFile = ""
Const DefaultQuery = "SELECT COUNT(*) FROM [Sheet1$];"
Dim files = Input("Excelファイル(.xlsx;.xls)をドロップしてください。複数ドロップ時は最後のファイルを使用します", DefaultFile)
If files = EMPTY Then ExitExit
Dim arr = Split(files, "<#TAB>")
Dim excelFile = arr[Resize(arr)]
If FOpen(excelFile, F_EXISTS) <> TRUE Then
    MsgBox("ファイル " + excelFile + " は存在しません。中断します")
    ExitExit
ElseIf Copy(excelFile, Length(excelFile) - 4) <> ".xlsx" And Copy(excelFile, Length(excelFile) - 3) <> ".xls" Then
    MsgBox("ファイル " + excelFile + " はExcelファイルではありません。中断します")
    ExitExit
EndIf
Dim query = Input("SQLを入力してください", DefaultQuery)
If query = EMPTY Then ExitExit
Dim objConnection = CreateOleObj("ADODB.Connection")
Try
    objConnection.Open("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=<#DBL>" + excelFile + "<#DBL>;Extended Properties=<#DBL>Excel 12.0;HDR=YES;IMEX=1;<#DBL>;")
Except
    MsgBox("Connectionオブジェクトの取得に失敗しました: " + TRY_ERRMSG + "<#CR>中断します")
    ExitExit
EndTry
objConnection.CursorLocation = adUseClient
Dim objRecordset = CreateOleObj("ADODB.Recordset")
Try
    //objRecordset.Open(query, objConnection, adOpenForwardOnly, adLockReadOnly)
    objRecordset.Open(query, objConnection)
Except
    MsgBox("クエリに失敗しました。恐らくSQL文が間違っています: " + TRY_ERRMSG + "<#CR>中断します")
    ExitExit
EndTry
Dim report = ""
If !objRecordset.EOF And objRecordset.Fields.Count > 0 Then
    // 見出しを作成
    report = report + objRecordset.Fields(0).Name
    For i = 1 To objRecordset.Fields.Count - 1
        report = report + "<#TAB>" + objRecordset.Fields(i).Name
    Next
    report = report + "<#CR>"
    // 結果を作成
    While !objRecordset.EOF
        report = report + objRecordset.Fields(0).Value
        For i = 1 To objRecordset.Fields.Count - 1
            report = report + "<#TAB>" + objRecordset.Fields(i).Value
        Next
        report = report + "<#CR>"
        objRecordset.MoveNext
    WEnd
    SendStr(0, report)
EndIf
objRecordset.Close()
objConnection.Close()
MsgBox("正常終了しました。結果はクリップボードにコピーされます")

Outlookで新規メールアイテムを作成

Const olMailItem = 0
// 送信先メールアドレス:複数指定時は ; で区切る
toAddress = "送り先メールアドレス"
ccAddress = ""
bccAddress = ""
// サブジェクト
subject = "TEST"
// %APPDATA%\Microsoft\Signatures\ フォルダ下にある署名ファイル(.txt)
signFile = "署名.txt"
TextBlock message
:(ここにメール文面)
EndTextBlock
objOutlook = GetActiveOleObj("Outlook.Application")
objMailItem = objOutlook.CreateItem(olMailItem)
With objMailItem
  If Length(toAddress) > 0 Then .To = toAddress
  If Length(ccAddress) > 0 Then .Cc = ccAddress
  If Length(bccAddress) > 0 Then .Bcc = bccAddress
  .Subject = subject
  .Body = message
  If Length(signFile) > 0 Then .Body = .Body + "<#CR>" + GetSignatures(signFile)
EndWith
objMailItem.Display(FALSE)
Function GetSignatures(file)
  fid = FOpen(GET_APPDATA_DIR + "\Microsoft\Signatures\" + file, F_READ)
  Result = FGet(fid, F_ALLTEXT)
  FClose(fid)
FEnd

Outlookのメール着信を監視

Try
  objOutlook = GetActiveOleObj("Outlook.Application")
Except
  MsgBox("Outlook が起動していません: " + TRY_ERRMSG + "<#CR>終了します", BTN_OK)
  ExitExit
EndTry
OleEvent(objOutlook, "ApplicationEvents_11", "NewMailEx", "OL_NewMailEx")
OleEvent(objOutlook, "ApplicationEvents_11", "Quit", "OL_Quit")
While TRUE
  Sleep(500)
WEnd
Procedure OL_NewMailEx(id)
  MsgBox("OL_NewMailEx() Called.")
FEnd
Procedure OL_Quit()
  ExitExit
FEnd

nMail.dll利用

// nMail.dllを使ったメール送信(nMail.dllはhttp://www.nanshiki.co.jp/からDL)
DEF_DLL NMailInitializeWinSock(): int: nMail
DEF_DLL NMailGetVersion(): int: nMail
DEF_DLL NMailSmtpSendMailPortNoW(Wstring, Wstring, Wstring, Wstring, Wstring, Wstring, Wstring, Wstring, Wstring, int, int): uint: nMail
DEF_DLL NMailEndWinSock(): int: nMail
NMailInitializeWinSock()
//MsgBox("NMailGetVersion = " + NMailGetVersion())
smtp = "SMTPサーバ名"
toAdr = "送信先メールアドレス"
ccAdr = NULL
bccAdr = NULL
fromAdr = "送信元メールアドレス"
subject = "サブジェクト"
body = "本文"
header = NULL
path = NULL
flag = 0
port = 25
MsgBox("NMailSmtpSendMailPortNoW = " + NMailSmtpSendMailPortNoW(smtp, toAdr, ccAdr, bccAdr, fromAdr, subject, body, header, path, flag, port))
NMailEndWinSock()

動作記録用Meryマクロ

「動作記録」で作成したUWSCファイルを加工するMery用マクロ(JScript)。

マウスの動きを遅らせる

「余分な時間、マウス移動は記録しない」をチェックした場合に使う。

#title = "SLEEP()を挿入する"
#tooltip = "マウスの動きを遅らせる"
// UWSCマクロにおいて一行ごとにSLEEP()を挿入する(操作を遅くする;キーボード操作中は対象外)
// 使い方:
//   1. 「動作記録」で作成したUWSCマクロをMeryで開く
//   2. マウスの動きを遅くしたい箇所を範囲選択する
//   3. 当マクロを実行
var w = "SLEEP(2)";  // 2秒ウェイトを入れる
var s1 = document.selection.Text.split("\n");
var s2 = new Array();
for (var i = 0; i < s1.length; i++) {
  s2.push(s1[i]);
  if (i < s1.length - 1) {
    if (s1[i].length > 0 && s1[i].substr(0, 4) != "KBD(")
      s2.push(w);
    else if (s1[i].substr(0, 4) == "KBD(" && s1[i+1].length > 0 && s1[i+1].substr(0, 4) != "KBD(")
      s2.push(w);
  }
}
document.selection.Text = s2.join("\n");

マウスの動きを速める

「余分な時間、マウス移動は記録しない」をチェックしていない場合に使う。

その1

#title = "MMV()の第三引数の値を一割小さくする(直接書換え)"
#tooltip = "マウスの動きを速める"
// UWSCマクロにおいてMMV()の第三引数の値を一割小さくする(マウスの動きを速める)
// 使い方:
//   1. 「動作記録」で作成したUWSCマクロをMeryで開く
//   2. マウスの動きを速めたい箇所を範囲選択する
//   3. 当マクロを実行
var p = 0.9;
var r1 = /^MMV\(\d+,\d+,(\d+)\)$/;
var r2 = /^(MMV\(\d+,\d+,)\d+(\))$/;
var s1 = document.selection.Text.split("\n");
var s2 = new Array();
for (var i = 0; i < s1.length; i++) {
  if (s1[i].substr(0, 4) == "MMV(") {
    x = parseFloat(s1[i].replace(r1, "$1"));
    if (isNaN(x)) {
      s2.push(s1[i]);
    } else {
      x2 = Math.ceil(x * p);
      s2.push(s1[i].replace(r2, "$1" + x2.toString() + "$2"));
    }
  }
  else
    s2.push(s1[i]);
}
document.selection.Text = s2.join("\n");

その2

#title = "MMV()の第三引数の値を一割小さくする(*0.9版)"
#tooltip = "マウスの動きを速める"
// UWSCマクロにおいてMMV()の第三引数の値を一割小さくする(マウスの動きを速める)
// 使い方:
//   1. 「動作記録」で作成したUWSCマクロをMeryで開く
//   2. マウスの動きを速めたい箇所を範囲選択する
//   3. 当マクロを実行
var p = 0.9;  // この値で速度調整
var r = /^(MMV\(\d+,\d+,\d+)(\))$/;
var s1 = document.selection.Text.split("\n");
var s2 = new Array();
for (var i = 0; i < s1.length; i++) {
  if (r.test(s1[i]))
    s2.push(s1[i].replace(r, "$1*" + p.toString() + "$2"));
  else
    s2.push(s1[i]);
}
document.selection.Text = s2.join("\n");

ランチメニュ向け

クリップボード中のフォルダのパスでエクスプローラを開く

DosCmd("explorer.exe " + Trim(GetStr(0)))

クリップボード中のURLでIEを開く

objIE = CreateOLEObj("InternetExplorer.Application")
objIE.Visible = TRUE
objIE.Navigate(Trim(GetStr(0)))

クリップボード中のURLでEdgeを開く

DosCmd("start microsoft-edge:" + Trim(GetStr(0)))

その他

  • 関数、手続きはメインロジックの下に記述せねばならない(凄い嵌った)

*1 アンチウィルスソフトによってはIExpressをブロックしてくる場合がある。その場合は諦めるかアンチウィルスソフトを無効化することになる。
*2 今回の設定でこの文字は表示されない。
*3 作りは適当なので関係ないエレメントも拾う。