# Build-XlsmFromSource.ps1 - ACP 3.2.0-RC1 # Builds Actuarial_Closing_Platform.xlsm from the two site downloads that are not blocked: # Actuarial_Closing_Platform_macro-free.xlsx and ACP_VBA_source.zip (or its extracted folder). # Does in Excel what the manual import does, plus the steps the manual import misses: # - ThisWorkbook code goes into the existing ThisWorkbook module (not a new class module "ThisWorkbook1"), # - the 46 button macro assignments are restored (the .xlsx has none), # - the Office and MSForms references are checked, # - workbook events stay off while building (Workbook_Open / BeforeSave do not run). # Every input is checked against its SHA-256; afterwards the code of every module is compared with the source and the # saved .xlsm is inspected (VBA project present, 46 button assignments). On any error no .xlsm is left behind. # Changes no Office or security setting. Needs "Trust access to the VBA project object model" (Excel > File > Options > # Trust Center > Trust Center Settings > Macro Settings); the script tells you if it is off. # Published on the download site as Build-XlsmFromSource.txt (plain text, pure ASCII). To use it: # 1. Put Build-XlsmFromSource.txt, Actuarial_Closing_Platform_macro-free.xlsx and ACP_VBA_source.zip in one folder. # 2. Open PowerShell in that folder (Explorer: click the address bar, type powershell, press Enter) and run the .txt as it is: # powershell -NoProfile -ExecutionPolicy Bypass -Command "& ([scriptblock]::Create((Get-Content -Raw .\Build-XlsmFromSource.txt)))" # or rename it to .ps1 and run: powershell -NoProfile -ExecutionPolicy Bypass -File .\Build-XlsmFromSource.ps1 [-Xlsx ..] [-Vba ..] [-OutFile ..] # 3. The last line says SONUC / RESULT; everything shown is also written to Build-XlsmFromSource.log in that folder. # TR: Uc dosyayi ayni klasore koyun, o klasorde PowerShell acin ve yukaridaki komutu calistirin (yeniden adlandirma gerekmez). [CmdletBinding()] param( [string]$Xlsx = '', [string]$Vba = '', [string]$OutFile = '' ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 $XlsxSha = '31718B775B50D99DDDB86456DCB560D9A306258347A070CDDF6A2E8A7EF178AA' $SrcSha = [ordered]@{ 'mod_Archive.bas' = '6C040083EDC1A60A0B12706DFB873230B6D8AC19E629DBC6A0E3238E03765137' 'mod_Cmd.bas' = '6B3C7436C635B3B73DA5CAC2AB007A1FD7590D9F26DFE3A2F8FE4272AD94CEF8' 'mod_ColumnMap.bas' = '293C02ECB0AB589A7EBA1C74F9DBBC9A46195B98BBBA6DCB64093C1B7CB1398F' 'mod_Export.bas' = '73769569C480240D313DFCE7808660DDE9E157DF595D7CE1C12A938C5E9E47C6' 'mod_Log.bas' = '4E52D0107202E688FBDC10C3B788FA6370DFBA6C5E5C7D0A69ED2550597C8E79' 'mod_Security.bas' = 'EECF9E28BABD716076419958197C2ED5D0E9ACDE5090A7DEB91C1DFC76EED043' 'mod_Session.bas' = '56AE4C3BF7BE78F7B8D06C9F9A0E8640D26EA3FAAD513AE1E6B6F07D1A8683FC' 'mod_Setup.bas' = '98DE2B37E12C120F24FCAEC4A501795C2760240CBEF5F9F02C9A1C65FD31F3C4' 'mod_Store.bas' = 'E7928EC0A92CEE096937B0AD9A41B2C4DE1DC749E7715FFDBDF8D1188BF5B336' 'mod_Util.bas' = '8590CCD044DF3F44C51F7AB8AC86A4BB76442E3F9724693B7E8C54EA740CB89C' 'frmPassword.frm' = '9EB9FA45F8D496BC3852132405BDE446D9D4AFB06CBC1967729A944CDA682988' 'frmPassword.frx' = '1B0B819E1A0A4E54F80268356263B2399610C097AADA636BD26DAD70CD8C1C88' 'ThisWorkbook.cls' = 'A23F327B972D108A349FF1608CAC68A9CEBB0AC53A4F6D55DB744636F2E99BEB' } # sheet | shape | macro, taken from the published .xlsm (all 46 non-empty macro assignments of its 22 drawings) $Buttons = @( 'Home|btnRefreshAll|Cmd_RefreshAll' 'Home|btnSubmitForReview|Cmd_SubmitForReview' 'Home|btnClosePeriod|Cmd_ClosePeriod' 'Home|btnRejectSubmission|Cmd_RejectSubmission' 'Home|btnExportClosingPack|Cmd_ExportClosingPack' 'Home|btnExportDashboardPDF|Cmd_ExportDashboardPDF' 'Home|btnSetup|Cmd_Setup' 'Home|btnAdminLogin|Cmd_AdminLogin' 'Home|btnAdminLogout|Cmd_AdminLogout' 'Home|btnManageUsers|Cmd_ManageUsers' 'Home|btnAddChangelogEntry|Cmd_AddChangelogEntry' 'Home|btnReopenPeriod|Cmd_ReopenPeriod' 'Home|btnVerifyLog|Cmd_VerifyLog' 'Home|btnShowStaging|Cmd_ShowStaging' 'Home|btnHideStaging|Cmd_HideStaging' 'Home|btnExportSourceCode|Cmd_ExportSourceCode' 'Home|btnClearLock|Cmd_ClearLock' 'Home|btnBackfill|Cmd_Backfill' 'Home|btnRebuildStore|Cmd_RebuildStore' 'Home|btnLockColumnMap|Cmd_LockColumnMap' 'Home|btnUnlockColumnMap|Cmd_UnlockColumnMap' 'ControlPanel|btnRefresh|Cmd_RefreshAll' 'ControlPanel|btnSubmitforreview|Cmd_SubmitForReview' 'ControlPanel|btnCloseperiod|Cmd_ClosePeriod' 'Dashboard|btnExportPDF|Cmd_ExportDashboardPDF' 'AN_Index|btnExportDashboardPDF|Cmd_ExportDashboardPDF' 'AN_Exec_Life|btnExportDashboardPDF|Cmd_ExportDashboardPDF' 'AN_Exec_Pension|btnExportDashboardPDF|Cmd_ExportDashboardPDF' 'AN_Exec_NonLife|btnExportDashboardPDF|Cmd_ExportDashboardPDF' 'AN_Trend|btnExportDashboardPDF|Cmd_ExportDashboardPDF' 'AN_Triangle|btnExportDashboardPDF|Cmd_ExportDashboardPDF' 'AN_Lags|btnExportDashboardPDF|Cmd_ExportDashboardPDF' 'AN_Severity|btnExportDashboardPDF|Cmd_ExportDashboardPDF' 'AN_Limits|btnExportDashboardPDF|Cmd_ExportDashboardPDF' 'AN_Anomaly|btnExportDashboardPDF|Cmd_ExportDashboardPDF' 'AN_Watchlist|btnExportDashboardPDF|Cmd_ExportDashboardPDF' 'AN_Backtest|btnExportDashboardPDF|Cmd_ExportDashboardPDF' 'AN_Dispersion|btnExportDashboardPDF|Cmd_ExportDashboardPDF' 'AN_TailFit|btnExportDashboardPDF|Cmd_ExportDashboardPDF' 'AN_RawView|btnRawView|Cmd_RawView' 'AN_RawView|btnRawExport|Cmd_RawExport' 'AN_RawView|btnOpenSourceFile|Cmd_OpenSourceFile' 'DataView|btnShowlines|Cmd_ViewCompanyMonth' 'DataView|btnExportlinestoExcel|Cmd_ExportCompanyMonth' 'ColumnMap|btnVerifyselectedColumnIDs|Cmd_VerifyColumnIDs' 'RefData|btnRefreshReference|Cmd_RefreshReference' ) function Say([string]$tr, [string]$en) { Write-Host (' OK ' + $tr) -ForegroundColor Green; Write-Host (' ' + $en) -ForegroundColor DarkGray } function Stop-Build([string]$tr, [string]$en) { Write-Host '' Write-Host ('HATA: ' + $tr) -ForegroundColor Red Write-Host ('ERROR: ' + $en) -ForegroundColor Red throw 'ACP_BUILD_STOPPED' } function Get-FileSha([string]$p) { (Get-FileHash -LiteralPath $p -Algorithm SHA256).Hash } function Get-CodeBody([string]$text) { # code after the last "Attribute" line, line endings and trailing blanks normalised (as the site build compares it) $lines = $text -replace "`r`n", "`n" -replace "`r", "`n" -split "`n" $last = -1 for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i].StartsWith('Attribute ')) { $last = $i } } $body = @(); for ($i = $last + 1; $i -lt $lines.Count; $i++) { $body += $lines[$i].TrimEnd() } $s = 0; while ($s -lt $body.Count -and -not $body[$s].Trim()) { $s++ } $e = $body.Count - 1; while ($e -ge $s -and -not $body[$e].Trim()) { $e-- } if ($e -lt $s) { return '' } return ($body[$s..$e] -join "`n") } Write-Host '' Write-Host 'ACP: .xlsx + VBA kaynagindan makrolu calisma kitabi / macro-enabled workbook from the .xlsx and the VBA source' -ForegroundColor Cyan Write-Host '' $xl = $null; $wb = $null; $work = $null; $exit = 1; $built = $null; $saved = '' $here = $PSScriptRoot if (-not $here) { $here = (Get-Location).ProviderPath } # run from a script block (the .txt as it is): the current folder $log = $false try { Start-Transcript -LiteralPath (Join-Path $here 'Build-XlsmFromSource.log') -Force | Out-Null; $log = $true } catch { } Write-Host (' Klasor / folder: ' + $here) try { if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') { Stop-Build ('PowerShell kisitli dil kipinde (' + $ExecutionContext.SessionState.LanguageMode + '); Excel otomasyonu kullanilamaz. BT ile gorusun.') ` ('PowerShell runs in ' + $ExecutionContext.SessionState.LanguageMode + ' mode; Excel automation is not available. Ask IT.') } # ------------------------------------------------------------ inputs if (-not $Xlsx) { $Xlsx = Join-Path $here 'Actuarial_Closing_Platform_macro-free.xlsx' } if (-not (Test-Path -LiteralPath $Xlsx -PathType Leaf)) { Stop-Build ('.xlsx bulunamadi: ' + $Xlsx) ('.xlsx not found: ' + $Xlsx) } $Xlsx = (Resolve-Path -LiteralPath $Xlsx).ProviderPath if ((Get-FileSha $Xlsx) -ne $XlsxSha) { Stop-Build ('.xlsx sitedeki 3.2.0-RC1 dosyasi degil ya da degismis. Siteden yeniden indirin; once Excel ile acip kaydetmeyin.') ` ('The .xlsx is not the 3.2.0-RC1 site file or was modified. Download it again; do not open and save it in Excel first.') } Say '.xlsx dogrulandi (SHA-256)' '.xlsx verified (SHA-256)' if (-not $Vba) { foreach ($c in @((Join-Path $here 'ACP_VBA_source.zip'), (Join-Path $here 'ACP_VBA_source'), $here)) { if ((Test-Path -LiteralPath $c -PathType Leaf) -or (Test-Path -LiteralPath (Join-Path $c 'mod_Cmd.bas'))) { $Vba = $c; break } } } if (-not $Vba -or -not (Test-Path -LiteralPath $Vba)) { Stop-Build 'ACP_VBA_source.zip (ya da cikarilmis klasoru) bulunamadi; -Vba ile yolunu verin.' 'ACP_VBA_source.zip (or its extracted folder) not found; pass its path with -Vba.' } $work = Join-Path ([System.IO.Path]::GetTempPath()) ('ACP_build_' + [guid]::NewGuid().ToString('N')) $src = Join-Path $work 'vba' New-Item -ItemType Directory -Path $src | Out-Null if (Test-Path -LiteralPath $Vba -PathType Leaf) { Expand-Archive -LiteralPath $Vba -DestinationPath $src } else { foreach ($f in $SrcSha.Keys) { if (Test-Path -LiteralPath (Join-Path $Vba $f)) { Copy-Item -LiteralPath (Join-Path $Vba $f) -Destination $src } } } foreach ($f in $SrcSha.Keys) { $p = Join-Path $src $f if (-not (Test-Path -LiteralPath $p)) { Stop-Build ('VBA kaynaginda eksik dosya: ' + $f) ('File missing in the VBA source: ' + $f) } if ((Get-FileSha $p) -ne $SrcSha[$f]) { Stop-Build ($f + ' sitedeki 3.2.0-RC1 kaynagi degil ya da degismis (satir sonlari CRLF kalmali). Zip dosyasini yeniden indirin.') ` ($f + ' is not the 3.2.0-RC1 site source or was modified (line endings must stay CRLF). Download the zip again.') } } Say ('VBA kaynagi dogrulandi (' + $SrcSha.Count + ' dosya, SHA-256)') ('VBA source verified (' + $SrcSha.Count + ' files, SHA-256)') if (-not $OutFile) { $OutFile = Join-Path (Split-Path -Parent $Xlsx) 'Actuarial_Closing_Platform.xlsm' } $OutFile = [System.IO.Path]::GetFullPath($OutFile) if (Test-Path -LiteralPath $OutFile) { Stop-Build ('Hedef dosya zaten var, uzerine yazilmaz: ' + $OutFile) ('The target file exists and is never overwritten: ' + $OutFile) } # ------------------------------------------------------------ Excel Write-Host ' ... Excel arka planda aciliyor (bir iki dakika surebilir) / starting Excel in the background' -ForegroundColor DarkGray $xl = New-Object -ComObject Excel.Application $xl.Visible = $false $xl.DisplayAlerts = $false $xl.ScreenUpdating = $false $xl.EnableEvents = $false $xl.AutomationSecurity = 3 # msoAutomationSecurityForceDisable: this instance runs no macro while building $wb = $xl.Workbooks.Open($Xlsx, 0, $false) $vbp = $null try { $vbp = $wb.VBProject; $null = $vbp.VBComponents.Count } catch { $vbp = $null } if ($null -eq $vbp) { Stop-Build ('Excel VBA projesine erisime izin vermiyor. Excel > Dosya > Secenekler > Guven Merkezi > Guven Merkezi Ayarlari > Makro Ayarlari > "VBA proje nesne modeline erisime guven" kutusunu isaretleyin ve betigi yeniden calistirin. Kutu gri ise ilke ile kapatilmistir; BT ile gorusun.') ` ('Excel refuses access to the VBA project. Tick Excel > File > Options > Trust Center > Trust Center Settings > Macro Settings > "Trust access to the VBA project object model" and run again. If it is greyed out, policy disables it; ask IT.') } foreach ($c in @($vbp.VBComponents)) { if ($c.Type -ne 100) { Stop-Build ('.xlsx beklenmedik bir VBA bileseni iceriyor: ' + $c.Name) ('The .xlsx holds an unexpected VBA component: ' + $c.Name) } } # modules and the form (Import reads the .frx next to the .frm) foreach ($f in $SrcSha.Keys) { if ($f -notmatch '\.(bas|frm)$') { continue } $comp = $vbp.VBComponents.Import((Join-Path $src $f)) $want = [System.IO.Path]::GetFileNameWithoutExtension($f) if ($comp.Name -ne $want) { Stop-Build ($f + ' ' + $comp.Name + ' adiyla geldi') ($f + ' was imported as ' + $comp.Name) } } # ThisWorkbook: the code goes into the existing document module $twbBody = Get-CodeBody ([System.IO.File]::ReadAllText((Join-Path $src 'ThisWorkbook.cls'), [System.Text.Encoding]::Default)) $cm = $vbp.VBComponents.Item($wb.CodeName).CodeModule if ($cm.CountOfLines -gt 0) { $cm.DeleteLines(1, $cm.CountOfLines) } $cm.AddFromString($twbBody) Say '10 modul, frmPassword formu ve ThisWorkbook kodu yerlestirildi' '10 modules, the frmPassword form and the ThisWorkbook code are in place' # references $refs = @{} foreach ($r in @($vbp.References)) { $refs[$r.Guid.ToUpperInvariant()] = $r.Description } foreach ($g in @('{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}', '{0D452EE1-E08F-101A-852E-02608C4D0BB4}')) { if (-not $refs.ContainsKey($g)) { $null = $vbp.References.AddFromGuid($g, 2, 0) } } $names = @(); foreach ($r in @($vbp.References)) { $names += $r.Description } Say ('Referanslar: ' + ($names -join ', ')) 'References checked (Office and MSForms present)' # code comparison (the VBA editor may re-case identifiers project-wide; letter case is not compared) $diff = @() foreach ($f in $SrcSha.Keys) { if ($f -notmatch '\.(bas|frm|cls)$') { continue } $name = [System.IO.Path]::GetFileNameWithoutExtension($f) if ($name -eq 'ThisWorkbook') { $name = $wb.CodeName } $m = $vbp.VBComponents.Item($name).CodeModule $have = '' if ($m.CountOfLines -gt 0) { $have = Get-CodeBody ($m.Lines(1, $m.CountOfLines)) } $want = Get-CodeBody ([System.IO.File]::ReadAllText((Join-Path $src $f), [System.Text.Encoding]::Default)) if ($have.ToLowerInvariant() -ne $want.ToLowerInvariant()) { $diff += $f } } if ($diff.Count -gt 0) { Stop-Build ('Kod kaynaktan farkli: ' + ($diff -join ', ')) ('Code differs from the source: ' + ($diff -join ', ')) } Say 'Her modulun kodu kaynakla ayni' 'The code of every module equals the source' # buttons $wb.Activate() $bad = @() foreach ($row in $Buttons) { $sheet, $shape, $macro = $row.Split('|') try { $s = $wb.Worksheets.Item($sheet).Shapes.Item($shape) $s.OnAction = $macro $back = [string]$s.OnAction if (-not ($back -eq $macro -or $back.EndsWith('!' + $macro))) { $bad += ($sheet + '!' + $shape + ' = ' + $back) } } catch { $bad += ($sheet + '!' + $shape + ': ' + $_.Exception.Message) } } if ($bad.Count -gt 0) { Stop-Build ('Dugme atanamadi: ' + ($bad -join '; ')) ('Button assignment failed: ' + ($bad -join '; ')) } Say ($Buttons.Count.ToString() + ' dugme makrosuna atandi') ($Buttons.Count.ToString() + ' buttons assigned to their macros') # saved straight into the target folder (checked above that no file of that name exists; removed again if a check fails) Write-Host (' ... Excel .xlsm olarak kaydediyor / Excel saves the .xlsm: ' + $OutFile) -ForegroundColor DarkGray $built = $OutFile $wb.SaveAs($OutFile, 52) # xlOpenXMLWorkbookMacroEnabled $saved = [string]$wb.FullName Write-Host (' ... Excel kaydettigi yeri bildiriyor / Excel reports the file saved as: ' + $saved) -ForegroundColor DarkGray $wb.Close($false); $wb = $null $xl.Quit() } catch { if ($_.Exception.Message -ne 'ACP_BUILD_STOPPED') { Write-Host '' Write-Host ('HATA / ERROR: ' + $_.Exception.Message) -ForegroundColor Red } if ($built -and (Test-Path -LiteralPath $built)) { Remove-Item -LiteralPath $built -Force -ErrorAction SilentlyContinue } $built = $null } finally { if ($null -ne $wb) { try { $wb.Close($false) } catch { } } if ($null -ne $xl) { try { $xl.Quit() } catch { } [void][System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($xl) $xl = $null [System.GC]::Collect(); [System.GC]::WaitForPendingFinalizers() } } # ------------------------------------------------------------ check the saved package try { if ($built) { for ($i = 0; $i -lt 20 -and -not (Test-Path -LiteralPath $built); $i++) { Start-Sleep -Milliseconds 500 } } if ($built -and -not (Test-Path -LiteralPath $built)) { $elsewhere = ($saved -and $saved -ne $built -and (Test-Path -LiteralPath $saved)) Write-Host '' if ($elsewhere) { Write-Host ('HATA: Excel dosyayi baska bir yere kaydetti: ' + $saved + ' (beklenen: ' + $built + ')') -ForegroundColor Red Write-Host ('ERROR: Excel saved the file elsewhere: ' + $saved + ' (expected: ' + $built + ')') -ForegroundColor Red } else { Write-Host ('HATA: Excel kaydettigini bildirdi (' + $saved + ') ama dosya diskte yok. En olasi neden: guvenlik yazilimi ya da ilke dosyayi kaydedildigi anda kaldirdi.') -ForegroundColor Red Write-Host ' Windows Guvenligi > Virus ve tehdit korumasi > Koruma gecmisi bolumune bakin; bu kaydi ve Build-XlsmFromSource.log dosyasini BT ile paylasin.' -ForegroundColor Red Write-Host ('ERROR: Excel reported the file saved (' + $saved + ') but it is not on disk. Most likely security software or a policy removed it as it was written;') -ForegroundColor Red Write-Host ' see Windows Security > Virus & threat protection > Protection history and share it and the log with IT.' -ForegroundColor Red } $built = $null } if ($built) { Add-Type -AssemblyName System.IO.Compression.FileSystem $z = [System.IO.Compression.ZipFile]::OpenRead($built) try { $hasVba = $null -ne $z.GetEntry('xl/vbaProject.bin') $found = @() foreach ($e in $z.Entries) { if ($e.FullName -notmatch '^xl/drawings/drawing\d+\.xml$') { continue } $rd = New-Object System.IO.StreamReader($e.Open()) try { $xml = $rd.ReadToEnd() } finally { $rd.Dispose() } foreach ($mm in [regex]::Matches($xml, ' macro="([^"]+)"')) { $found += ($mm.Groups[1].Value -replace '^.*!', '') } } } finally { $z.Dispose() } $expected = @(); foreach ($row in $Buttons) { $expected += $row.Split('|')[2] } $same = ($found.Count -eq $expected.Count) -and ($found.Count -gt 0) -and (@(Compare-Object ($expected | Sort-Object) ($found | Sort-Object)).Count -eq 0) if (-not $hasVba -or -not $same) { Write-Host '' Write-Host ('HATA / ERROR: kaydedilen dosya denetimi gecmedi (VBA projesi: ' + $hasVba + ', dugme atamalari ' + $found.Count + ' / ' + $expected.Count + ')') -ForegroundColor Red Remove-Item -LiteralPath $built -Force -ErrorAction SilentlyContinue } else { Say ('Kaydedilen dosya denetlendi: VBA projesi var, ' + $found.Count + ' dugme atamasi') ('Saved file checked: VBA project present, ' + $found.Count + ' button assignments') Write-Host '' Write-Host (' ' + $OutFile) -ForegroundColor Cyan Write-Host '' Write-Host ' Sonraki adim: dosyayi paylasimdaki platform klasorune kopyalayin; kurulum kilavuzu bolum 3 (Guvenilir Konum / imza).' -ForegroundColor White Write-Host ' "VBA proje nesne modeline erisime guven" kutusunu bu is icin actiysaniz simdi yeniden kapatabilirsiniz.' -ForegroundColor White Write-Host ' Next: copy the file to the platform folder on the share; install guide section 3. You may now untick' -ForegroundColor DarkGray Write-Host ' "Trust access to the VBA project object model" again if you ticked it for this.' -ForegroundColor DarkGray $exit = 0 } } } catch { Write-Host '' Write-Host ('HATA / ERROR: kaydedilen dosya denetlenemedi / the saved file could not be checked: ' + $_.Exception.Message) -ForegroundColor Red if ($built -and (Test-Path -LiteralPath $built)) { Remove-Item -LiteralPath $built -Force -ErrorAction SilentlyContinue } $exit = 1 } finally { if ($work -and (Test-Path -LiteralPath $work)) { Remove-Item -LiteralPath $work -Recurse -Force -ErrorAction SilentlyContinue } } Write-Host '' if ($exit -eq 0) { Write-Host 'SONUC: BASARILI - .xlsm olusturuldu / RESULT: SUCCESS - the .xlsm was built' -ForegroundColor Green } else { Write-Host 'SONUC: BASARISIZ - .xlsm olusturulmadi; nedeni yukaridaki HATA satirinda / RESULT: FAILED - no .xlsm; see the ERROR line above' -ForegroundColor Red } if ($log) { Write-Host ' (Kayit / log: Build-XlsmFromSource.log)' -ForegroundColor DarkGray try { Stop-Transcript | Out-Null } catch { } } exit $exit