フォルダ監視バッチ

特定のフォルダを監視しておいて、特定のファイルが作成されたら別の処理を実行するバッチを作成してみた。
用途としては、OCR処理をするにあたって、特定の共通フォルダを監視しておいて、特定のファイルが作成されたらOCRジョブを実行する。
もちろんOCR以外にも特定のファイルがおかれたら別処理を実行するような仕組みもできる。

HTAアプリケーションで作ろうかとも思ったけどサポートがいつまで続くかわからないし、たいして画面に表示することもないので、いろいろ考えた結果、バッチで十分じゃないのかなぁと。


<処理例>
 ・「CheckFolder.txt」に監視したいフォルダ名を複数行入力しておく
 ・監視バッチが5秒おきに実行し、「CheckFolder.txt」を読み込んで1行ずつ読み込む
   ・指定されたフォルダに「Job.txt」が作成されているかチェックする
   ・Job.txtが作成されていたら、ソフトを起動するコマンドを実行する
    処理後にフォルダ名を変更するなどの処理が必要なら必要に応じてVBSを実行する
    VBSにも引数を渡すことができる
 ・最初の処理に戻る

◆フォルダを監視するバッチ
-------- CheckFolder.txtの内容 ------

C:\Users\SampleUser\Desktop\Yosiki1
C:\Users\SampleUser\Desktop\Yosiki2

----------------------------------------

-------- バッチの内容 ------
@echo off
:loop

rem ReadFolderを開く
echo 処理を終了する場合はCtrl+Cを押下してください。
echo --- 処理実行 ---

for /f %%v in (CheckFolder.txt) do (
echo [%%v]をチェックします...
if exist %%v\Job.txt (
echo VBSを実行する
) else (
echo Job.txtが存在しません
)
)

rem 5秒待ちます
timeout 5

rem コマンドプロンプトの内容をクリア
cls

goto loop
------------------------------

VBAでStringを高速に扱う

Insert分を19万件分実行している処理が非常に遅い。
SQLを100件ずつまとめて実行しているけどやはり遅い。
文字列結合で遅くなっているので.NETであるStringBuilderと同じようなものがないか調べたところ、作っているサイトがあったのでメモ。
https://excel-ubara.com/excelvba4/EXCEL271.html

ー---
Option Explicit

Private sBuf As String
Private iBuf As Long

Private Sub Class_Initialize()
    sBuf = String$(32768, vbNullChar)
    iBuf = 0
End Sub

Public Sub Append(ByRef sValue As String)
    If iBuf + Len(sValue) > Len(sBuf) Then
        sBuf = sBuf & String$(CLng(Len(sBuf) * 2) + Len(sValue), vbNullChar)
    End If
    Mid(sBuf, iBuf + 1) = sValue
    iBuf = iBuf + Len(sValue)
End Sub

Public Function ToString() As String
    ToString = Left$(sBuf, iBuf)
End Function

VBA Recordsetの利用について

 VBAでDataTableに似たものを利用したいと思っていろいろと調べているとRecordsetがいい感じとのことだったので調べて必要そうなところを抜粋してメモしている。

Private Sub Sample()
    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual
    
'▽Recordse定義
     Dim myRS As ADODB.Recordset
     Set myRS = New ADODB.Recordset
     With myRS.Fields
          .Append "商品番号", adVarWChar, -1
          .Append "番号", adVarWChar, -1
     End With
     myRS.Open

'▽データ取り込み-----------------------------------------
    Dim i As Long
    Dim FieldList As Variant: FieldList = Array("商品番号", "番号")
    i = 2
    Do
        myRS.AddNew FieldList, CreateArrayData(Sheet2.Range("A" & i & ":B" & i).Resize(1, 2).Value)
        i = i + 1
    Loop Until IsEmpty(Sheet2.Cells(i, 1))
     
     myRS.MoveFirst
    
'Recordsetデータ検索-----------------------------------------
    i = 2
    Do
        myRS.Filter = "商品番号 = '" & Sheet1.Cells(i, 1).Value & "'"
        Sheet1.Cells(i, 2) = myRS!番号
        i = i + 1
        If i Mod 1000 = 0 Then
        End If
    Loop Until IsEmpty(Sheet1.Cells(i, 1))
'閉じる
     myRS.Close
     Set myRS = Nothing

    Application.ScreenUpdating = True
    Application.Calculation = xlCalculationAutomatic
    
End Sub

Function CreateArrayData(SecDimArray As Variant) As Variant
     Dim myVar As Variant
     Dim i As Long
     ReDim myVar(0 To UBound(SecDimArray, 2) - 1)
     For i = 0 To UBound(myVar)
          myVar(i) = SecDimArray(1, i + 1)
     Next
     CreateArrayData= myVar
End Function

備忘録 Having句で集計関数を利用する

 HAVING句をうまく利用する
 
 意外と思いつかなかったけど、HAVING句でCASE句を利用することができる
 ※例は適当です。グループの中で条件を満たしていないものがあるかどうかのチェックができます。
 
  ・会社ごとに振込先口座を持っている
  ・グループ会社の中で異なる振込先があるグループがあるか
 
   (例)
    
   
    会社CD |   会社名   | グループ会社CD | 請求会社CD | 親会社CD | 銀行CD | 支店CD | 口座番号 | 口座名義人
    00001  |テスト1    | 001            | 00001      |  00001   |  0001  |  002   | 1234567  | カ)テスト1
    00002  |テスト1-1   | 001            | 00002      |  00001   |  0001  |  002   | 0004567  | カ)テスト2
    00003  |テスト1-2   | 001            | 00001      |  00001   |  0001  |  002   | 1234567  | カ)テスト1
    10001  |テスト2    | 002            | 10001      |  10001   |  0001  |  002   | 1234567  | カ)テスト2
    10002  |テスト2-1   | 002            | 10001      |  10001   |  0001  |  002   | 1234567  | カ)テスト2
    
    
    -- 「請求先」が「親会社」になっていないデータが混じっているグループ会社を抽出
  -- グループCDで集約を行い、グループCDでまとめた件数=レコードの中で「請求会社CD」
  SELECT グループ会社CD
  FROM 契約会社テーブル
  GROUP BY グループ会社CD
  HAVING COUNT(*) <> SUM(CASE 
                        WHEN 請求会社CD = 親会社CD THEN 1 
                        ELSE 0 
                    END) AS 抽出対象
  上記のデータ例で行くと「グループ会社CD:001」が抽出される
  
 集計関数の中にもCASE式が書けるって初めて知ったときに結構衝撃だったけど、月ごとの集計結果を列で持ちたいときとかにも使えるので素晴らしい。
 
  SELECT 
         商品品種名
        ,SUM(CASE WHEN SUBSTR(売上日,5,2) = '01' THEN 売上金額 ELSE 0 END) AS 1月売上
        ,SUM(CASE WHEN SUBSTR(売上日,5,2) = '02' THEN 売上金額 ELSE 0 END) AS 2月売上
        ,SUM(CASE WHEN SUBSTR(売上日,5,2) = '03' THEN 売上金額 ELSE 0 END) AS 3月売上
        ,SUM(CASE WHEN SUBSTR(売上日,5,2) = '04' THEN 売上金額 ELSE 0 END) AS 4月売上
             ・
             ・
             ・
  FROM 売上テーブル
  WHERE 売上日 BETWEEN '20210101' AND '20211231'
  GROUP BY 商品品種名
             
  
  
 

Excel VBAで郵便番号・住所からカスタマーバーコードを作成

 Excelマクロでカスタマーバーコードのもとになるデータを作る必要があったので、調べてみたらいい古い記事だけどわかりやすいプログラムを記載しているブログがあった。

https://shiganaisenotes.blogspot.com/2011/11/blog-post.html

一部うまく変換できなかったところがあったので、その個所を修正して全文を転記させてもらった。例)〇〇丁目〇番地 七番館 101号室 の場合に「七番館」が変換できない

使い方としては、VBAの標準モジュールにコードを張り付けて「参照設定」に「Microsoft VBScript Regular Expressions 5.5」を追加すれば使うことができる。
変換した結果を、Wordの差し込み印刷でバーコードフィールドを利用することでカスタマーバーコードを利用することができ、郵送代を減らすことができる。ハズ。

---------------------------------------------------------------------------
Public Const FIND_NUM As String = "壱弐参〇一二三四五六七八九十百千"
Public Const FIND_TEXT As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
'参照設定を追加する
'手順:[ツール] -> [参照設定] -> Microsoft VBScript Regular Expressions 5.5をチェック
Public Function CreateCustomerBarCode(strZip As String, ByVal strAddress As String) As String
    Dim strRetZip       As String
    Dim strRetAddress   As String
    Dim strChar         As String
    Dim blnConvFlg      As Boolean   'ハイフンに置き換え済みフラグ
    Dim intLoop         As Integer
    Dim i               As Integer
    Dim j               As Integer
    Dim intCount        As Integer
    Dim intPos          As Integer
    Dim intTextLength   As Integer
    Dim intTargetPos    As Integer
    Dim strFindKeyword  As String
    Dim strTargetText   As String
    Dim strConvertText  As String
    Dim strTemp()       As String
    Dim strResult       As String
    Dim varConvertNum   As Variant
    Dim varDeleteText   As Variant
    Dim intFindPos      As Integer
    Dim objRegExp       As RegExp   '参照設定要(Microsoft VBScript Regular Expressions 5.5)
    Dim objMatchCollect As MatchCollection
    Dim objMatch        As Match
    Dim strTarget()     As String
    Dim intMatchPos()   As Integer
   
    varConvertNum = Array("1", "2", "3", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "100", "1000")
   
    varDeleteText = Array("&", "&", "/", "/", "・", "・", ".", ".")
   
    '郵便番号にハイフンがあったら除外します
    strRetZip = Replace(strZip, "-", "", , , vbTextCompare)
   
    '住所の全角文字を半角に変換します
    strAddress = StrConv(strAddress, vbNarrow)
     
    '住所に含まれる記号を除外します
    For intLoop = 1 To UBound(varDeleteText)
      strAddress = Replace(strAddress, varDeleteText(intLoop), "", , , vbTextCompare)
    Next intLoop
    '2つ以上連続したアルファベットを「-」へ置換する
    Set objRegExp = New RegExp
    '正規表現で2つ以上連続したアルファベットを検索し、「-」へ置換する。
    objRegExp.Pattern = "[a-zA-Z][a-zA-Z]+"
    objRegExp.Global = True '複数該当する場合に対応
    strAddress = objRegExp.Replace(strAddress, "-")
   
    '抜き出しの補足ルール
    'http://www.post.japanpost.jp/zipcode/zipmanual/p19.html
   
    '1. 漢数字が下記の特定文字の前にある場合は抜き出し対象とし、算用数字に変換して抜き出します。
    '特定文字群(9種類) "丁目"  "丁 " "番地"  "番"  "号"  "地割"  "線"  "の"  "ノ"
    
    '1つずつ見ていく
    For i = 1 To 9
        strFindKeyword = ""
        Select Case i
            Case 1
                strFindKeyword = "丁目"
            Case 2
                strFindKeyword = "丁"
            Case 3
                strFindKeyword = "番地"
            Case 4
                strFindKeyword = "番"
            Case 5
                strFindKeyword = "号"
            Case 6
                strFindKeyword = "地割"
            Case 7
                strFindKeyword = "線"
            Case 8
                strFindKeyword = "の"
            Case 9
                strFindKeyword = "ノ"
        End Select
       
        Dim ii As Integer
        intPos = 0
        ii = 0
        
        '〇〇丁目〇番地 七番館 101号室 の場合に「七番館」が変換できないため、キーワードでループ
        Do
             '当該キーワードが見つかるか?
             If ii = 0 Then
                intPos = InStr(strAddress, strFindKeyword)
             Else
                '同じキーワードで二回目、三回目をする場合は探す住所を前回の検索結果以降にする
                intPos = InStr(Mid$(strAddress, ii), strFindKeyword)
             
                '見つかった場合は、探す住所から何文字目かどうかをチェックする
                If intPos > 0 Then
                   intPos = intPos + ii - Len(strFindKeyword)
                End If
             
             End If
             ii = ii + intPos + Len(strFindKeyword)
            
             '見つかったら変換処理を行う。
             If intPos > 0 Then
                 '見つかったところから左へ1つずつ移動し漢数字じゃなくなったら終了
                 For intCount = intPos To 2 Step -1
                     strTargetText = Mid$(strAddress, (intCount - 1), 1)
                     intTargetPos = InStr(FIND_NUM, strTargetText)
                    
                     '漢数字なら継続
                     If intTargetPos > 0 Then
                     Else
                         '漢数字以外ならループを抜ける
                         Exit For
                     End If
                 Next intCount
                
                 strResult = ""
                
                 'カウントが開始位置と同一なら漢数字なしと判定して何もしない。
                 If intPos = intCount Then
                 Else
                     strConvertText = Mid$(strAddress, intCount, (intPos - intCount))
                     intTextLength = Len(strConvertText)
                     ReDim strTemp(intTextLength)
                     For j = 1 To intTextLength
                         intTargetPos = InStr(FIND_NUM, Mid$(strConvertText, j, 1))
                         strTemp(j) = varConvertNum(intTargetPos - 1)
                     Next j
                    
                     strResult = "0"
                    
                     '計算して数値を求める
                     For j = 1 To intTextLength
                         If (j = 1) Then
                             strResult = strTemp(j)
                         Else
                             '2桁以上なら前の桁と掛け算して足す
                             If Len(strTemp(j)) >= 2 Then
                                 'すでに代入されている値が2桁以上なら足す
                                 If Len(strResult) >= 2 Then
                                     strResult = CStr(CInt(strResult) + (CInt(strTemp(j - 1)) * CInt(strTemp(j))))
                                 Else
                                     '代入されている数字が1桁なら、そのまま代入
                                     strResult = CStr(CInt(strTemp(j - 1)) * CInt(strTemp(j)))
                                 End If
                             Else
                                 '1つ前が2桁以上か?
                                 If Len(strTemp(j - 1)) >= 2 Then
                                     '最終桁だったら足す
                                     If j = intTextLength Then
                                         strResult = CStr(CInt(strResult) + CInt(strTemp(j)))
                                     Else
                                         'それ以外だったら何もしないで次にまわす
                                     End If
                                 Else
                                     '1桁なら結合する
                                     strResult = strResult & strTemp(j)
                                 End If
                             End If
                         End If
                     Next j
                 End If
                 '変換した結果があれば処理する
                 If Len(strResult) = 0 Then
                 Else
                     strAddress = Left$(strAddress, (intCount - 1)) & strResult & Mid$(strAddress, intPos)
                 End If
             End If
        Loop While intPos > 0
        
    Next i
 
    '住所から数字部分だけを取り出して"("で区切ります
    strRetAddress = ""
   
    blnConvFlg = True '先頭の数字以外の文字は無視する設定
   
    For intLoop = 1 To Len(strAddress)
      '1文字取り出し
      strChar = Mid$(strAddress, intLoop, 1)
     
        If IsNumeric(strChar) Then
           
            '数字のとき
            strRetAddress = strRetAddress & strChar
            blnConvFlg = False
         
        Else
       
            'ハイフンに置き換え済みでないとき
            If Not blnConvFlg Then
                'アルファベットだったらそのままくっつける
                intFindPos = InStr(FIND_TEXT, strChar)
                If intFindPos > 0 Then
                    If intFindPos = 6 Then  'Fだったら特別扱い->"-"に置き換え
                        strRetAddress = strRetAddress & "-"
                    Else
                        If Right$(strRetAddress, 1) = "-" Then
                            strRetAddress = strRetAddress & strChar
                        Else
                            strRetAddress = strRetAddress & "-" & strChar & "-"
                        End If
                    End If
                Else
                    strRetAddress = strRetAddress & "-"
                End If
                blnConvFlg = True
            Else
                'アルファベットだったらそのままくっつける
                intFindPos = InStr(FIND_TEXT, strChar)
                If intFindPos > 0 Then
                    If Right$(strRetAddress, 1) = "-" Then
                        strRetAddress = strRetAddress & strChar
                    Else
                        strRetAddress = strRetAddress & "-" & strChar & "-"
                    End If
                Else
                    strRetAddress = strRetAddress & "-"
                End If
                blnConvFlg = True
            End If
        End If
    Next intLoop
   
    '最終処理
   
    '連続したハイフンは1つする
    objRegExp.Pattern = "\-\-+"
    objRegExp.Global = True '複数該当する場合に対応
    strRetAddress = objRegExp.Replace(strRetAddress, "-")
   
    '最後と先頭のハイフンを除去します
   
    If Left$(strRetAddress, 1) = "-" Then
      strRetAddress = Mid$(strRetAddress, 2)
    End If
   
    If Right$(strRetAddress, 1) = "-" Then
      strRetAddress = Left$(strRetAddress, Len(strRetAddress) - 1)
    End If
   
    '(アルファベットの前後の-(ハイフン)は取り除きます)
    objRegExp.Pattern = "-*[a-zA-Z]-*"
    objRegExp.Global = True '複数該当する場合に対応
   
    'パターンマッチするものをCollectionへ格納
    Set objMatchCollect = objRegExp.Execute(strRetAddress)
    '1つ以上あれば以降の処理を継続
    If objMatchCollect.Count > 0 Then
        '配列を再定義
        ReDim strTarget(objMatchCollect.Count)
        ReDim intMatchPos(objMatchCollect.Count)
       
        '複数該当に対応する
        For i = 0 To (objMatchCollect.Count - 1)
            'Collectionの情報を展開
            Set objMatch = objMatchCollect.Item(i)
            '-(ハイフン)をスペースへ変換して格納
            strTarget(i + 1) = Replace(objMatch.Value, "-", " ", , , vbTextCompare)
            '文字列が見つかった場所を格納
            intMatchPos(i + 1) = objMatch.FirstIndex
        Next i
       
        '実際に置き換える
        For i = 1 To objMatchCollect.Count
            strRetAddress = Left$(strRetAddress, intMatchPos(i)) & strTarget(i) & Mid$(strRetAddress, (intMatchPos(i) + Len(strTarget(i)) + 1))
        Next i
        '文字列中の空白を削って終了
        strRetAddress = Replace(strRetAddress, " ", "", , , vbTextCompare)
       
    End If
    '変換された郵便番号と住所を結合して返します
    CreateCustomerBarCode = strRetZip & strRetAddress
End Function

Accessがインストールされていない環境でExcelからAccessファイルを作る

 Accessは使いこなせば非常に有効だけど、結構高い。

使いこなせばすぐに費用対効果は出せるけど、一般の人には理解してもらえないところがある。

ただExcelはほぼ必ずと言っていいほど入っているので、Excelが入っていない環境でAccessファイルを作ることができることがわかった。


<必要な参照設定>


<コード>

’Accessファイル(.accdb)を作成

Public Sub CreateAccdbFile()

    Dim objMdb As DAO.Database                                      

    Set objMdb = DBEngine.CreateDatabase("C:\Users\SampleUser\Desktop\sample.accdb", dbLangJapanese)

End Sub

これでAccessファイルだけは作成できる。データベースの最適化の処理などはないので、ひたすら肥大化し続けることあるので注意すること。
まぁSqlite3.dllがある64bit環境であればsqlite3を利用するのが一番だと思うけども。
後はA5 SQL Mk-2などで作成されたファイルに接続して、Create Tableを実行すればいい。


バーコード読み取りソフト

Excelでバーコードを読み取りたいと思ったけど、dllをsystem32へコピーする必要があったりとちょっと今の環境ではできないことだったので、専用のexeを作ってVectorで公開した。

  https://www.vector.co.jp/soft/winnt/writing/se523178.html


使い方はとっても簡単。Zxing.exeの引数にファイルパスを指定するだけ。

例)

 ・コマンドプロンプトを起動

 ・Zxing.exeのあるフォルダへ移動

 ・Zxing.exe c:\users\ユーザー名\DeskTop\バーコード画像.jpg

 ・Result.txtに結果が出力される


トリミングをする必要があるので、Excelのトリミング機能を使って、トリミングした後の画像を一時的にtemp.bmpとかに保存しておいてその画像をZxing.exeに渡してあげる。


PowerShellでWPFアプリケーションをBindingするときの注意点

 1.PowerShellでWPFアプリケーションのBindingについて マニアックだけどPowerShellでWPFアプリケーションを作っている。INotifyChangedを実装したいけど、PowerShellにはgetterやsetterがないので通知が発行できない。 そ...