コマンドプロンプトで出来ないことをするために、PowerShell のお世話になることがある。Windows の標準機能なのでユーザーを選ばないのが利点。
ただ、こういったニーズが頻繁にある訳でないのと、結構「クセ強」な言語なので、しばらく使わないとすっかり忘れてしまう。
という訳で備忘のため、Excel のマクロのインポート、エキスポートを行うスクリプトを書いてみた。こうすると、Git でマクロのバージョン管理が出来るようになる。
先ずはエキスポート。こちらは比較的楽に完成。
function Usage() {
Write-Host "`n Export VBComponents`n"
Write-Host " Usage : export (-h | foo.xlsm)`n"
exit 0
}
if ($($Args.Count) -eq 1) {
$arg = $Args[0]
if ($arg -eq "-h") { Usage }
else {
if ($arg -notlike "*.xlsm") {
Write-Host "`n ERROR : invalid file type"
exit 1
}
if ((Test-Path -Path $arg)) {
$workbookPath = Resolve-Path -Path $arg
} else {
Write-Host "`n ERROR : $arg not found"
exit 1
}
}
} else { Usage }
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$excel.DisplayAlerts = $false
$excel.AutomationSecurity = 1 # msoAutomationSecurityLow
$workbook = $excel.Workbooks.Open($workbookPath)
foreach ($VBComponent in $workbook.VBProject.VBComponents) {
switch ($VBComponent.Type) {
1 {$ext = ".bas"}
2 {$ext = ".cls"}
3 {$ext = ".frm"}
default {$ext = ".cls"}
}
$VBComponent.Export($PWD.Path + '\' + $VBComponent.Name + $ext)
}
$workbook.Close($false)
$excel.Quit()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($workbook) | Out-Null
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
次いで、インポート。Excel のエキスポートの仕様として、ワークシート(Thisworkbook を含む)の VBA は拡張子に「.cls」を持つファイルに保存される。これは「クラス」に属するファイルとバッティングする。しかも、これをインポートする時は「クラス」のメタデータを除いて取り込まなければならない。
この辺を考慮して書いたコードが以下。ちょっと苦労。
function Usage() {
Write-Host "`n Import VBComponents`n"
Write-Host " Usage : import (-h | foo.xlsm)`n"
exit 0
}
function is_include($test, $items) {
foreach ($item in $items) {
if ($item -eq $test) {
return $True
}
}
return $False
}
function Import($item_name, $codeFilePath, $is_sheet) {
try {
if ($is_sheet) {
$lines = Get-Content -Path $codeFilePath
$match_1st = $false
$codeLines = @()
foreach ($line in $lines) {
$match = $line.Trim() -like "Attribute*"
if ((-not $match_1st) -and $match) {
$match_1st = $True
}
if ((-not $match_1st) -or $match) {
continue
}
$codeLines += $line
}
$codeContent = $codeLines | Out-String
$item = $workbook.VBProject.VBComponents.Item($item_name)
$code = $item.CodeModule
$code.DeleteLines(1, $code.CountOfLines)
$code.AddFromString($codeContent)
} else {
$components = $workbook.VBProject.VBComponents
$item = $components.Item($item_name)
$components.Remove($item)
$components.Import($codeFilePath)
}
return $True
}
catch {
return $False
}
}
if ($($Args.Count) -eq 1) {
$arg = $Args[0]
if ($arg -eq "-h") { Usage }
else {
if ($arg -notlike "*.xlsm") {
Write-Host "`n ERROR : invalid file type"
exit 1
}
if ((Test-Path -Path $arg)) {
$workbookPath = Resolve-Path -Path $arg
$exist_wbk = $True
} else {
$currentPath = Get-Location
$workbookPath = Join-Path -Path $currentPath -ChildPath $arg
$exist_wbk = $False
}
}
} else { Usage }
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$excel.DisplayAlerts = $false
$excel.AutomationSecurity = 1 # msoAutomationSecurityLow
if ($exist_wbk) {
$workbook = $excel.Workbooks.Open($workbookPath)
} else {
$workbook = $excel.Workbooks.Add()
}
$sheets = @()
foreach ($sheet in $workbook.Sheets) {
$sheets += $sheet.CodeName
}
$sheets += "Thisworkbook"
foreach ($file in (Get-ChildItem -Path ".\*" `
-Include *.cls, *.bas, *.frm)) {
$module_name = $file.BaseName
if ($file.Extension -eq ".cls") {
$include = is_include -test $module_name -items $sheets
} else {
$include = $False
}
$success = Import -item_name $module_name `
-codeFilePath $file.FullName `
-is_sheet $include
if (-not $success) {
Write-Host "fail to import $module_name"
}
}
if ($exist_wbk) {
$workbook.Save()
} else {
$fileFormat = 52 # 52 : xlOpenXMLWorkbookMacroEnabled
$workbook.SaveAs($workbookPath, $fileFormat)
}
$workbook.Close($false)
$excel.Quit()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($workbook) | Out-Null
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
それぞれ、PowerShell を開くのが面倒なので、バッチファイルから起動できるようにしておく。
@echo off
powershell %~dp0export_vba.ps1 %*
@echo off
powershell %~dp0import_vba.ps1 %*
途中、AI の助けも借りたが、当方の聞き方が悪いのか、先方の知識ベースが少ないのか、微妙な所ではあまりアテにならなかった。