c:\___Fire\Docs\GML\Interpreter>powershell ./parse_robot_intermediate_monads.ps1 -RobotRoot "C:\___Fire\Robot" -OutDir "C:\___Fire\Docs\GML\Interpreter" -WritePerFile C:\___Fire\Docs\GML\Interpreter\parse_robot_intermediate_monads.ps1 : The property 'Count' cannot be found on this object. Verify that the property exists. At line:1 char:1 + ./parse_robot_intermediate_monads.ps1 -RobotRoot C:\___Fire\Robot -Ou ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [parse_robot_intermediate_monads.ps1], PropertyNotFoundException + FullyQualifiedErrorId : PropertyNotFoundStrict,parse_robot_intermediate_monads.ps1 here again is my copy: # parse_robot_intermediate_monads.ps1 # # powershell ./parse_robot_intermediate_monads.ps1 ` # -RobotRoot "C:\___Fire\Robot" ` # -OutDir "C:\___Fire\Docs\GML\Interpreter" ` # -WritePerFile # # powershell ./parse_robot_intermediate_monads.ps1 -RobotRoot "C:\___Fire\Robot" -OutDir "C:\___Fire\Docs\GML\Interpreter" -WritePerFile # param( [Parameter(Mandatory=$true)][string]$RobotRoot, # e.g. C:\___Fire\Robot [Parameter(Mandatory=$true)][string]$OutDir, # e.g. C:\___Fire\Docs\GML\Interpreter [switch]$WritePerFile # optional: emit per-file monads.zson too ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" function FileInfoObj([string]$p){ $fi = Get-Item -LiteralPath $p -ErrorAction Stop return [pscustomobject]@{ path = $fi.FullName sizeBytes = [int64]$fi.Length lastWriteUtc = $fi.LastWriteTimeUtc.ToString("o") } } function Read-QuotedPair([string]$line) { $m = [regex]::Matches($line, '"([^"]*)"') $vals = @() foreach($x in $m){ $vals += $x.Groups[1].Value } return $vals } function Read-TypeAndStack([string]$line) { # Expected: "Exec" % "letter --> ordinal" (stack is comment-only) $vals = Read-QuotedPair $line $type = $null $stack = $null if($vals.Count -ge 1){ $type = $vals[0] } if($vals.Count -ge 2){ $stack = $vals[1] } return @($type, $stack) } function Parse-NewdefFile([string]$defFile){ $lines = Get-Content -LiteralPath $defFile -Encoding UTF8 $N = $lines.Count $monads = New-Object System.Collections.Generic.List[object] $i = 0 while($i -lt $N){ $line = $lines[$i] $pair = Read-QuotedPair $line if($pair.Count -ge 2){ $verb = $pair[0] $title = $pair[1] if(($i + 2) -ge $N){ break } $line2 = $lines[$i+1] $typeStack = Read-TypeAndStack $line2 $mType = $typeStack[0] $stackComment = $typeStack[1] $line3 = $lines[$i+2] $trim3 = $line3.TrimStart() if($trim3.Length -eq 0){ $i += 1; continue } $q = $trim3.Substring(0,1) # delimiter char (first non-white) $rest = $trim3.Substring(1) $sb = New-Object System.Text.StringBuilder $lineStart = $i+3 $lineEnd = $i+3 $closed = $false $iClose = $i+2 function AppendLine([string]$s){ [void]$sb.Append($s) [void]$sb.Append("`n") } $idx = $rest.IndexOf($q) if($idx -ge 0){ [void]$sb.Append($rest.Substring(0, $idx)) $closed = $true $iClose = $i+2 $lineEnd = $i+3 } else { AppendLine $rest $j = $i+3 while($j -lt $N){ $lj = $lines[$j] $idx2 = $lj.IndexOf($q) if($idx2 -ge 0){ [void]$sb.Append($lj.Substring(0, $idx2)) $closed = $true $iClose = $j $lineEnd = $j+1 break } else { AppendLine $lj } $j++ } } if($closed){ $content = $sb.ToString() if($content.EndsWith("`n")){ $content = $content.Substring(0, $content.Length - 1) } $monads.Add([pscustomobject]@{ verb = $verb title = $title type = $mType stackComment = $stackComment contentQuote = $q content = $content lineStart = $lineStart lineEnd = $lineEnd }) $i = $iClose + 1 continue } } $i++ } return $monads } # ---- find files ---- if(-not (Test-Path -LiteralPath $RobotRoot)){ throw "RobotRoot not found: $RobotRoot" } if(-not (Test-Path -LiteralPath $OutDir)){ New-Item -ItemType Directory -Path $OutDir | Out-Null } $allTxt = Get-ChildItem -LiteralPath $RobotRoot -Recurse -File -Filter *.txt # “definitt” and friends: $targets = @() # 1) definitt (any file starting with definitt_ OR exactly definitt*.txt) $targets += $allTxt | Where-Object { $_.Name -match '^definitt(_|$)' } # 2) def*_description.txt $targets += $allTxt | Where-Object { $_.Name -match '^def.*_description\.txt$' } # 3) addedefs_description.txt $targets += $allTxt | Where-Object { $_.Name -ieq 'addedefs_description.txt' } # de-dupe by full path $targets = $targets | Sort-Object FullName -Unique if($targets.Count -eq 0){ throw "No target defs files found under $RobotRoot (definitt*, def*_description, addedefs_description)." } # ---- parse ---- $combined = New-Object System.Collections.Generic.List[object] $byVerb = @{} # verb -> object w/ locations + latest title/type/content seen (first wins) $perFile = New-Object System.Collections.Generic.List[object] foreach($f in $targets){ $fi = FileInfoObj $f.FullName $monads = Parse-NewdefFile $f.FullName $perFile.Add([pscustomobject]@{ file = $fi count = $monads.Count }) foreach($m in $monads){ # add per-file provenance fields $entry = [pscustomobject]@{ verb = $m.verb title = $m.title type = $m.type stackComment = $m.stackComment contentQuote = $m.contentQuote content = $m.content src = $fi lineStart = $m.lineStart lineEnd = $m.lineEnd } $combined.Add($entry) if(-not $byVerb.ContainsKey($m.verb)){ $byVerb[$m.verb] = [pscustomobject]@{ verb = $m.verb title = $m.title type = $m.type stackComment = $m.stackComment # keep a short preview for quick browsing contentPreview = ($m.content.Substring(0, [Math]::Min(120, $m.content.Length))) locations = New-Object System.Collections.Generic.List[object] } } $byVerb[$m.verb].locations.Add([pscustomobject]@{ path = $fi.path sizeBytes = $fi.sizeBytes lastWriteUtc = $fi.lastWriteUtc lineStart = $m.lineStart lineEnd = $m.lineEnd }) } if($WritePerFile){ $outFileDir = Join-Path $OutDir ("perfile") if(-not (Test-Path -LiteralPath $outFileDir)){ New-Item -ItemType Directory -Path $outFileDir | Out-Null } $outMon = Join-Path $outFileDir ($f.BaseName + ".monads.zson") $payloadOne = [pscustomobject]@{ srcFile = $fi count = $monads.Count monads = $monads } ($payloadOne | ConvertTo-Json -Depth 12) | Set-Content -LiteralPath $outMon -Encoding UTF8 } } # ---- outputs ---- $stamp = (Get-Date).ToUniversalTime().ToString("yyyyMMdd_HHmmss_fff") $outCombined = Join-Path $OutDir ("Robot_IntermediateMonads_{0}.zson" -f $stamp) $outIndexCsv = Join-Path $OutDir ("Robot_IntermediateMonads_Index_{0}.csv" -f $stamp) $verbIndex = $byVerb.Values | Sort-Object verb $payload = [pscustomobject]@{ robotRoot = $RobotRoot timeUtc = (Get-Date).ToUniversalTime().ToString("o") fileCount = $targets.Count monadInstanceCount = $combined.Count verbCount = $verbIndex.Count files = $perFile verbs = $verbIndex monadInstances = $combined } ($payload | ConvertTo-Json -Depth 14) | Set-Content -LiteralPath $outCombined -Encoding UTF8 # concise CSV of verb -> #locations $verbIndex | Select-Object verb,title,type,stackComment,@{n="locationsCount";e={$_.locations.Count}},contentPreview | Export-Csv -LiteralPath $outIndexCsv -NoTypeInformation -Encoding UTF8 Write-Host ("WROTE: {0}" -f $outCombined) Write-Host ("WROTE: {0}" -f $outIndexCsv) Write-Host ("FILES: {0} INSTANCES: {1} UNIQUE VERBS: {2}" -f $targets.Count, $combined.Count, $verbIndex.Count) if($WritePerFile){ Write-Host ("PER-FILE: {0}" -f (Join-Path $OutDir "perfile")) } c:\___Fire\Docs\GML\OfficeBridge>powershell .\office_vba_to_vbnet_skeleton_v1.ps1 -VbaFolder "C:\___Fire\Docs\GML\Office\VBA\mackserve_vba_export" -OutVB "C:\___Fire\Docs\GML\OfficeBridge\MackServe\VBNet_Skeleton_FromVBA.vb" C:\___Fire\Docs\GML\OfficeBridge\office_vba_to_vbnet_skeleton_v1.ps1 : The property 'Count' cannot be found on this object. Verify that the property exists. At line:1 char:1 + .\office_vba_to_vbnet_skeleton_v1.ps1 -VbaFolder C:\___Fire\Docs\GML\ ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [office_vba_to_vbnet_skeleton_v1.ps1], PropertyNotFoundException + FullyQualifiedErrorId : PropertyNotFoundStrict,office_vba_to_vbnet_skeleton_v1.ps1 c:\___Fire\Docs\GML\OfficeBridge>powershell .\office_vba_to_vbnet_skeleton_v1.ps1 -VbaFolder "C:\\___Fire\\Docs\\GML\\Office\\VBA\\mackserve_vba_export" -OutVB "C:\\___Fire\\Docs\\GML\\OfficeBridge\\MackServe\\VBNet_Skeleton_FromVBA.vb" C:\___Fire\Docs\GML\OfficeBridge\office_vba_to_vbnet_skeleton_v1.ps1 : The property 'Count' cannot be found on this object. Verify that the property exists. At line:1 char:1 + .\office_vba_to_vbnet_skeleton_v1.ps1 -VbaFolder C:\\___Fire\\Docs\\G ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [office_vba_to_vbnet_skeleton_v1.ps1], PropertyNotFoundException + FullyQualifiedErrorId : PropertyNotFoundStrict,office_vba_to_vbnet_skeleton_v1.ps1 powershell is trying to be too cute with the command line, you can tell by putting a backslash at the end of the vbafoler parameter my powershell then complains about no string term, it ate the doble-quote, so what all that means is no more paths in powershell invocation, all objects need to be as you have there and then we can avoid the apparently confusing special chars and if all is relative to GML we can lose the "s if we maintain no spaces rule for all of our names below GML. So it may be worth just changing out the paths for environment vars, that said i may have erred in the way i copied it so make sure in new ver that i didn't mess up here is my copied and updated (just info) powershell file office_vba_to_vbnet_skeleton_v1.ps1 un officebridge: # # office_vba_to_vbnet_skeleton_v1.ps1 # # powershell .\office_vba_to_vbnet_skeleton_v1.ps1 -VbaFolder "C:\___Fire\Docs\GML\Office\VBA\mackserve_vba_export" -OutVB "C:\___Fire\Docs\GML\OfficeBridge\MackServe\VBNet_Skeleton_FromVBA.vb" # # # param( [Parameter(Mandatory=$true)][string]$VbaFolder, # folder containing exported .bas/.cls/.frm [Parameter(Mandatory=$true)][string]$OutVB # output .vb file ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" function Normalize-Line([string]$s){ # keep it simple; avoid any quote escaping inside PS itself return $s.Replace("`r","") } function Convert-SubSignatureToFunction([string]$line){ # Example: Public Sub Foo(ByVal x As Variant) # Return a function signature; default return As String $m = [regex]::Match($line, '^\s*(Public|Private|Friend)?\s*Sub\s+([A-Za-z_][A-Za-z0-9_]*)\s*(\([^)]*\))?', 'IgnoreCase') if(-not $m.Success){ return $null } $vis = $m.Groups[1].Value $name = $m.Groups[2].Value $args = $m.Groups[3].Value if([string]::IsNullOrWhiteSpace($args)){ $args = "()" } # replace Variant with Wariant in args $args2 = $args -replace '\bVariant\b', 'Wariant' if([string]::IsNullOrWhiteSpace($vis)){ $vis = "Public" } return ("{0} Function {1}{2} As String" -f $vis, $name, $args2) } function Convert-FunctionSignature([string]$line){ # Replace Variant -> Wariant in function args and return types $s = $line -replace '\bVariant\b', 'Wariant' return $s } function LooksLikeSubCallNeedingParens([string]$line){ # heuristics: starts with identifier and has space separated args, and not "Set ", not "If ", not "For ", etc. $t = $line.Trim() if($t -eq ""){ return $false } if($t.StartsWith("'")){ return $false } if($t -match '^(If|For|While|Do|Select|Case|End|Else|Dim|Set|Call|With|On Error|Exit|Option)\b'){ return $false } if($t -match '^\w+\s*\('){ return $false } # already has parens if($t -match '^\w+\s*=\s*'){ return $false } # assignment if($t -match '^\w+\.\w+'){ return $false } # object.member (could still be sub call, but skip) # identifier then at least one space then something return ($t -match '^[A-Za-z_]\w*\s+.+') } function Rewrite-SubCallToFunctionCall([string]$line){ # Turn: Foo a, b => dummy = Foo(a, b) $t = $line.Trim() $m = [regex]::Match($t, '^([A-Za-z_]\w*)\s+(.+)$') if(-not $m.Success){ return $line } $name = $m.Groups[1].Value $args = $m.Groups[2].Value.Trim() return ("dummy = {0}({1})" -f $name, $args) } # Gather input files $files = Get-ChildItem -LiteralPath $VbaFolder -File | Where-Object { $_.Extension -in @(".bas",".cls",".frm") } if($files.Count -eq 0){ throw "No VBA export files (.bas/.cls/.frm) found in $VbaFolder" } $sb = New-Object System.Text.StringBuilder # Header + Wariant [void]$sb.AppendLine("' Auto-generated VB.NET skeleton from VBA exports") [void]$sb.AppendLine("' - Sub -> Function As String (for VBA parens-friendly calls)") [void]$sb.AppendLine("' - Variant -> Wariant (surrogate discriminated union)") [void]$sb.AppendLine("Option Strict Off") [void]$sb.AppendLine("Option Explicit On") [void]$sb.AppendLine("") [void]$sb.AppendLine("Public Class Wariant") [void]$sb.AppendLine(" Public Enum WariantKind") [void]$sb.AppendLine(" Empty = 0") [void]$sb.AppendLine(" Str = 1") [void]$sb.AppendLine(" Dbl = 2") [void]$sb.AppendLine(" Bool = 3") [void]$sb.AppendLine(" Obj = 4") [void]$sb.AppendLine(" End Enum") [void]$sb.AppendLine(" Public Kind As WariantKind = WariantKind.Empty") [void]$sb.AppendLine(" Public StrValue As String = Nothing") [void]$sb.AppendLine(" Public DblValue As Double = 0") [void]$sb.AppendLine(" Public BoolValue As Boolean = False") [void]$sb.AppendLine(" Public ObjValue As Object = Nothing") [void]$sb.AppendLine("End Class") [void]$sb.AppendLine("") foreach($f in $files){ [void]$sb.AppendLine("\'============================================================") [void]$sb.AppendLine(("\' SOURCE: {0}" -f $f.FullName)) [void]$sb.AppendLine("\'============================================================") [void]$sb.AppendLine("\' BEGIN ORIGINAL VBA (commented)") $raw = Get-Content -LiteralPath $f.FullName -Encoding UTF8 foreach($ln in $raw){ [void]$sb.AppendLine(("\' " + (Normalize-Line $ln))) } [void]$sb.AppendLine("\' END ORIGINAL VBA") [void]$sb.AppendLine("") # Skeleton module wrapper per file $modName = [IO.Path]::GetFileNameWithoutExtension($f.Name) -replace '[^A-Za-z0-9_]', '_' [void]$sb.AppendLine(("Public Module {0}" -f $modName)) $inProc = $false $procName = "" foreach($ln0 in $raw){ $ln = (Normalize-Line $ln0) # Sub signature? $funcSig = Convert-SubSignatureToFunction $ln if($funcSig){ $inProc = $true $procName = ([regex]::Match($funcSig, 'Function\s+([A-Za-z_]\w*)', 'IgnoreCase')).Groups[1].Value [void]$sb.AppendLine(" " + $funcSig) [void]$sb.AppendLine(" ' TODO: translate body") [void]$sb.AppendLine(" Dim dummy As String = """"") [void]$sb.AppendLine(" Return dummy") [void]$sb.AppendLine(" End Function") continue } # Function signature line (keep as-is but Variant->Wariant) if($ln -match '^\s*(Public|Private|Friend)?\s*Function\s+'){ $inProc = $true $sig = Convert-FunctionSignature $ln [void]$sb.AppendLine(" " + $sig.Trim()) [void]$sb.AppendLine(" ' TODO: translate body") [void]$sb.AppendLine(" Return Nothing") [void]$sb.AppendLine(" End Function") continue } # End Sub / End Function (we already closed stubs) if($ln -match '^\s*End\s+Sub\b' -or $ln -match '^\s*End\s+Function\b'){ $inProc = $false $procName = "" continue } } [void]$sb.AppendLine("End Module") [void]$sb.AppendLine("") } # Write output [IO.File]::WriteAllText($OutVB, $sb.ToString(), (New-Object System.Text.UTF8Encoding($false))) Write-Host ("WROTE: {0}" -f $OutVB) Write-Host ("SRCFOLDER: {0}" -f $VbaFolder) Write-Host ("FILES: {0}" -f $files.Count) the single quote is a nuisance, but the real issue is the get-content within the append-line param space. c:\___Fire\Docs\GML\Office>powershell C:\___Fire\Docs\GML\Office\convert_mackserve_vba_to_vbnet.ps1 -VbaExportDir "C:\___Fire\Docs\GML\Office\VBA\mackserve_vba_export" -OutFile "C:\___Fire\Docs\GML\OfficeBridge\MackServe\MackServe_Mirror.vb" -TreatEventSubsAsFunctions At C:\___Fire\Docs\GML\Office\convert_mackserve_vba_to_vbnet.ps1:125 char:22 + [void]$sb.AppendLine(Get-Content -Raw -LiteralPath ( Join-Path $PSScr ... + ~ Missing ')' in method call. At C:\___Fire\Docs\GML\Office\convert_mackserve_vba_to_vbnet.ps1:125 char:22 + [void]$sb.AppendLine(Get-Content -Raw -LiteralPath ( Join-Path $PSScr ... + ~~~~~~~~~~~ Unexpected token 'Get-Content' in expression or statement. At C:\___Fire\Docs\GML\Office\convert_mackserve_vba_to_vbnet.ps1:125 char:130 + ... h $PSScriptRoot "vb_helpers_wariant_and_compat.vb" ) -Encoding UTF8 ) + ~ Unexpected token ')' in expression or statement. At C:\___Fire\Docs\GML\Office\convert_mackserve_vba_to_vbnet.ps1:160 char:35 + $endToken = $subSig.Success ? 'End Sub' : 'End Function' + ~ Unexpected token '?' in expression or statement. + CategoryInfo : ParserError: (:) [], ParseException + FullyQualifiedErrorId : MissingEndParenthesisInMethodCall c:\___Fire\Docs\GML\Office> again including the actual script for convert_mackserve_vba_to_vbnet.ps1: #### convert_mackserve_vba_to_vbnet.ps1 <# convert_mackserve_vba_to_vbnet.ps1 pwsh C:\___Fire\Docs\GML\Office\convert_mackserve_vba_to_vbnet.ps1 ` -VbaExportDir "C:\___Fire\Docs\GML\Office\VBA\mackserve_vba_export" ` -OutFile "C:\___Fire\Docs\GML\OfficeBridge\MackServe\MackServe_Mirror.vb" ` -TreatEventSubsAsFunctions powershell C:\___Fire\Docs\GML\Office\convert_mackserve_vba_to_vbnet.ps1 -VbaExportDir "C:\___Fire\Docs\GML\Office\VBA\mackserve_vba_export" -OutFile "C:\___Fire\Docs\GML\OfficeBridge\MackServe\MackServe_Mirror.vb" -TreatEventSubsAsFunctions Converts exported VBA modules (.bas/.cls/.frm) into a compileable VB.NET "mirror" file. Key transforms: - Non-event "Sub" => "Function ... As String" returning "OK" - Inject dummy assignment at call sites for functions produced by above rule - Variant => Wariant - Preserve original VBA as comments above each converted member - Emits a single VB.NET file including Wariant + VbaCompat helpers This is not intended to be runnable; it is an analysis/bridge artifact for better tooling + later back-port. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$VbaExportDir, # e.g. C:\___Fire\Docs\GML\Office\VBA\mackserve_vba_export [Parameter(Mandatory=$true)] [string]$OutFile, # e.g. C:\___Fire\Docs\GML\OfficeBridge\MackServe\MackServe_Mirror.vb [switch]$TreatEventSubsAsFunctions # optional: turn event handlers into Functions too ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" function Ensure-Dir([string]$path) { $dir = Split-Path -Parent $path if ($dir -and -not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null } } function Is-EventSignature([string]$signatureLine) { # crude but practical: "Private Sub X(...)" with " _" continuation handled earlier # detect common Excel event patterns: # Worksheet_*, Workbook_*, UserForm_*, CommandButton*_Click, etc. $nameMatch = [regex]::Match($signatureLine, '^\s*(Public|Private|Friend)?\s*Sub\s+([A-Za-z_][A-Za-z0-9_]*)\b', 'IgnoreCase') if (-not $nameMatch.Success) { return $false } $n = $nameMatch.Groups[2].Value return ($n -match '^(Workbook_|Worksheet_|UserForm_|CommandButton|ToggleButton|TextBox|ComboBox|ListBox|CheckBox|OptionButton|SpinButton|ScrollBar)') ` -or ($signatureLine -match '\bHandles\b') } function Normalize-Line([string]$line) { # Basic normalizations for VB.NET parsing # Replace VBA-only type aliases if needed $line = $line -replace '\bVariant\b', 'Wariant' return $line } function Convert-ParamList([string]$paramList) { # Converts "Optional x As Variant" => "Optional x As Wariant" $p = $paramList -replace '\bVariant\b', 'Wariant' # VBA allows "As Integer" etc; VB.NET is similar enough for compileability. return $p } function Convert-SubSignatureToFunction([string]$sigLine) { # Input: "Private Sub Foo(a As Long, Optional b As Variant)" # Output: "Private Function Foo(a As Long, Optional b As Wariant) As String" $m = [regex]::Match($sigLine, '^\s*(Public|Private|Friend)?\s*Sub\s+([A-Za-z_][A-Za-z0-9_]*)\s*\((.*)\)\s*$', 'IgnoreCase') if (-not $m.Success) { return $null } $vis = $m.Groups[1].Value $name = $m.Groups[2].Value $params = $m.Groups[3].Value $params = Convert-ParamList $params $prefix = "" if ($vis) { $prefix = "$vis " } return "$prefix" + "Function $name(" + $params + ") As String" } function Convert-FunctionSignature([string]$sigLine) { # Keep VB Functions but normalize Variant->Wariant $sigLine = $sigLine -replace '\bVariant\b', 'Wariant' return $sigLine } function Read-VbaFile([string]$path) { # VBA exports may be ANSI/UTF8; try UTF8 then fallback try { return Get-Content -LiteralPath $path -Encoding UTF8 } catch { return Get-Content -LiteralPath $path } } # Collect source files if (-not (Test-Path -LiteralPath $VbaExportDir)) { throw "VbaExportDir not found: $VbaExportDir" } $srcFiles = Get-ChildItem -LiteralPath $VbaExportDir -Recurse -File | Where-Object { $_.Extension -in @(".bas",".cls",".frm") } | Sort-Object FullName Ensure-Dir $OutFile $sb = New-Object System.Text.StringBuilder # Header [void]$sb.AppendLine("\' AUTO-GENERATED VB.NET MIRROR (compileable, not intended runnable)") [void]$sb.AppendLine("\' Source: exported MackServe VBA modules") [void]$sb.AppendLine("\' Rules: Subs->Functions (String), Variant->Wariant, callsites dummy-assign") [void]$sb.AppendLine("\' GeneratedUtc: " + (Get-Date).ToUniversalTime().ToString("o")) [void]$sb.AppendLine("") [void]$sb.AppendLine("Option Strict Off") [void]$sb.AppendLine("Option Explicit On") [void]$sb.AppendLine("Imports System") [void]$sb.AppendLine("Imports System.Collections.Generic") [void]$sb.AppendLine("") # Helper: Wariant + VbaCompat [void]$sb.AppendLine("\'") [void]$sb.AppendLine(Get-Content -Raw -LiteralPath ( Join-Path $PSScriptRoot "vb_helpers_wariant_and_compat.vb" ) -Encoding UTF8 ) [void]$sb.AppendLine("\'") [void]$sb.AppendLine("") # Track subs turned into functions so we can rewrite call sites $subToFunc = New-Object System.Collections.Generic.HashSet[string]([StringComparer]::OrdinalIgnoreCase) foreach ($f in $srcFiles) { $lines = Read-VbaFile $f.FullName $rel = $f.FullName.Substring($VbaExportDir.TrimEnd('\').Length).TrimStart('\') [void]$sb.AppendLine("\' ===================================================================") [void]$sb.AppendLine("\' FILE: " + $rel) [void]$sb.AppendLine("\' ===================================================================") [void]$sb.AppendLine("") # We wrap each file as a Module for compileability. # (You can later split Modules/Classes more faithfully if desired.) $moduleName = ("MackServe_" + ($f.BaseName -replace '[^A-Za-z0-9_]', '_')) [void]$sb.AppendLine("Public Module " + $moduleName) [void]$sb.AppendLine("") $i = 0 while ($i -lt $lines.Count) { $line = Normalize-Line $lines[$i] # Preserve VBA as comments for context # Detect member start: Sub or Function (single-line signature only; good enough for most exports) $subSig = [regex]::Match($line, '^\s*(Public|Private|Friend)?\s*Sub\s+[A-Za-z_][A-Za-z0-9_]*\s*\(.*\)\s*$', 'IgnoreCase') $funSig = [regex]::Match($line, '^\s*(Public|Private|Friend)?\s*Function\s+[A-Za-z_][A-Za-z0-9_]*\s*\(.*\)\s*As\s+\w+', 'IgnoreCase') if ($subSig.Success -or $funSig.Success) { # capture VBA block until End Sub/End Function $vbaBlock = New-Object System.Text.StringBuilder $startLine = $i $endToken = $subSig.Success ? 'End Sub' : 'End Function' while ($i -lt $lines.Count) { $raw = $lines[$i] [void]$vbaBlock.AppendLine($raw) if ($raw.Trim().Equals($endToken, [StringComparison]::OrdinalIgnoreCase)) { break } $i++ } # emit VBA comment [void]$sb.AppendLine(" \' --- VBA ORIGINAL ---") foreach ($vbLn in $vbaBlock.ToString().Split("`n")) { $vv = $vbLn.TrimEnd("`r") [void]$sb.AppendLine(" \' " + $vv) } [void]$sb.AppendLine(" \' --- /VBA ORIGINAL ---") # Now emit converted signature $sigLine = Normalize-Line $lines[$startLine] if ($subSig.Success) { $isEvent = Is-EventSignature $sigLine if ($isEvent -and -not $TreatEventSubsAsFunctions) { # keep as Sub, just normalize Variant->Wariant in params $sigLine = $sigLine -replace '\bVariant\b', 'Wariant' [void]$sb.AppendLine(" " + $sigLine.Trim()) [void]$sb.AppendLine(" \' NOTE: Event-style Sub preserved as Sub for mirror.") [void]$sb.AppendLine(" \' TODO: Implement body in .NET emulator if ever needed.") [void]$sb.AppendLine(" End Sub") } else { $fnSig = Convert-SubSignatureToFunction $sigLine if ($null -eq $fnSig) { # fallback $sigLine = $sigLine -replace 'Sub', 'Function' $fnSig = $sigLine + " As String" } # capture name for callsite rewrite $nm = [regex]::Match($fnSig, '\bFunction\s+([A-Za-z_][A-Za-z0-9_]*)\b', 'IgnoreCase') if ($nm.Success) { $subToFunc.Add($nm.Groups[1].Value) | Out-Null } [void]$sb.AppendLine(" " + $fnSig.Trim()) [void]$sb.AppendLine(" \' NOTE: Former VBA Sub converted to Function for safer round-trip.") [void]$sb.AppendLine(" \' TODO: Port logic as needed; for now compileable stub.") [void]$sb.AppendLine(" Return ""OK""") [void]$sb.AppendLine(" End Function") } } else { # existing Function: keep, normalize Variant->Wariant $fnSig = Convert-FunctionSignature $sigLine [void]$sb.AppendLine(" " + $fnSig.Trim()) [void]$sb.AppendLine(" \' TODO: Port function body; compileable stub.") [void]$sb.AppendLine(" Return Nothing") [void]$sb.AppendLine(" End Function") } [void]$sb.AppendLine("") $i++ continue } $i++ } [void]$sb.AppendLine("End Module") [void]$sb.AppendLine("") } # Optional: a summary of Sub->Function names (for your later VBA rewrite pass) [void]$sb.AppendLine("\' ===================================================================") [void]$sb.AppendLine("\' Sub->Function converted names (dummy assign at call sites recommended):") foreach ($n in $subToFunc) { [void]$sb.AppendLine("\' " + $n) } [void]$sb.AppendLine("\' ===================================================================") [void]$sb.AppendLine("") Set-Content -LiteralPath $OutFile -Value $sb.ToString() -Encoding UTF8 Write-Host "WROTE: $OutFile" Write-Host ("SRCFILES: {0} SUB2FUNC: {1}" -f $srcFiles.Count, $subToFunc.Count) still may be due to some format crossover like back-quote or other things that might be format-related. I have seen single quotes and back-quotes converted like in word. Check that so i can fix my end. we will move to a yson format for tree transactions. using the mapped character set makes json appear to allow any character even tab, lf, cr as labels: or anywhere there is a need to deflect special characters to a remapped ascii that will work in xml, ?son. If we adopt the .yson specifically for tree ops, that may help integrate with other ?son family members