Add Sage Spain export artifacts

This commit is contained in:
2026-05-07 14:09:32 +02:00
parent 6717843f18
commit 8477894758
42 changed files with 167193 additions and 0 deletions
@@ -0,0 +1,16 @@
$scriptPath = Join-Path $PSScriptRoot "Export-SageSqlCsv.ps1"
& $scriptPath `
-Database "Sage" `
-ObjectName @(
"dbo.CabeceraAlbaranCliente",
"dbo.LineasAlbaranCliente",
"dbo.EstadisVenta",
"dbo.EstadisVentaTallas",
"dbo.FacturasTB",
"dbo.MovimientosFacturas",
"dbo.Vis_RTDV_EfectosFactura"
) `
-FromDate "2025-01-01" `
-ToDate "2026-01-01" `
-MaxRowsPerObject 10000
@@ -0,0 +1,15 @@
$scriptPath = Join-Path $PSScriptRoot "Export-SageSqlCsv.ps1"
& $scriptPath `
-Database "Sage" `
-ObjectName @(
"dbo.CabeceraAlbaranCliente",
"dbo.LineasAlbaranCliente",
"dbo.EstadisVenta",
"dbo.EstadisVentaTallas",
"dbo.FacturasTB",
"dbo.MovimientosFacturas",
"dbo.Vis_RTDV_EfectosFactura"
) `
-FromDate "2025-01-01" `
-ToDate "2026-01-01"
@@ -0,0 +1,215 @@
param(
[string]$ServerInstance = "localhost",
[string]$Database = "Sage",
[datetime]$FromDate = "2025-01-01",
[datetime]$ToDate = "2026-01-01",
[string]$OutputDirectory = (Join-Path $env:USERPROFILE "Desktop")
)
$ErrorActionPreference = "Stop"
function New-Connection {
$builder = New-Object System.Data.SqlClient.SqlConnectionStringBuilder
$builder["Data Source"] = $ServerInstance
$builder["Initial Catalog"] = $Database
$builder["Integrated Security"] = $true
$builder["TrustServerCertificate"] = $true
$builder["Connect Timeout"] = 15
return New-Object System.Data.SqlClient.SqlConnection($builder.ConnectionString)
}
function Convert-ToCsvValue {
param($Value)
if ($null -eq $Value -or $Value -is [System.DBNull]) {
return ""
}
if ($Value -is [datetime]) {
$text = $Value.ToString("yyyy-MM-dd HH:mm:ss")
}
else {
$text = [string]$Value
}
$text = $text.Replace('"', '""')
return '"' + $text + '"'
}
function Export-QueryToCsv {
param(
[string]$Sql,
[string]$Path
)
$conn = New-Connection
$cmd = $conn.CreateCommand()
$cmd.CommandText = $Sql
$cmd.CommandTimeout = 0
$fromParameter = $cmd.Parameters.Add("@FromDate", [System.Data.SqlDbType]::Date)
$fromParameter.Value = $FromDate.Date
$toParameter = $cmd.Parameters.Add("@ToDate", [System.Data.SqlDbType]::Date)
$toParameter.Value = $ToDate.Date
$writer = New-Object System.IO.StreamWriter($Path, $false, [System.Text.Encoding]::UTF8)
$rowCount = 0
$salesSum = [decimal]0
try {
$conn.Open()
$reader = $cmd.ExecuteReader()
$headers = for ($i = 0; $i -lt $reader.FieldCount; $i++) {
Convert-ToCsvValue $reader.GetName($i)
}
$writer.WriteLine(($headers -join ";"))
$salesIndex = -1
for ($i = 0; $i -lt $reader.FieldCount; $i++) {
if ($reader.GetName($i) -eq "SalesPriceValue") {
$salesIndex = $i
break
}
}
while ($reader.Read()) {
$values = for ($i = 0; $i -lt $reader.FieldCount; $i++) {
Convert-ToCsvValue $reader.GetValue($i)
}
$writer.WriteLine(($values -join ";"))
$rowCount++
if ($salesIndex -ge 0 -and -not $reader.IsDBNull($salesIndex)) {
$salesSum += [decimal]$reader.GetValue($salesIndex)
}
}
}
finally {
$writer.Dispose()
$conn.Dispose()
}
return [pscustomobject]@{
Rows = $rowCount
SalesPriceValueSum = $salesSum
}
}
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$runDirectory = Join-Path $OutputDirectory "Sage_Spain_Sales_Export_$timestamp"
New-Item -ItemType Directory -Path $runDirectory -Force | Out-Null
$csvPath = Join-Path $runDirectory "Spain_Sales_2025.csv"
$summaryPath = Join-Path $runDirectory "Spain_Sales_2025_summary.txt"
$sql = @"
SELECT
'TRES' AS TSC,
'Spanien' AS Land,
'Sage' AS SourceSystem,
c.CodigoEmpresa AS CompanyCode,
c.EjercicioAlbaran AS DeliveryYear,
c.SerieAlbaran AS DeliverySeries,
c.NumeroAlbaran AS DeliveryNumber,
c.EjercicioFactura AS InvoiceYear,
c.SerieFactura AS InvoiceSeries,
c.NumeroFactura AS InvoiceNumber,
l.Orden AS PositionOnInvoice,
l.LineasPosicion AS SourceLineId,
l.CodigoArticulo AS Material,
l.DescripcionArticulo AS Name,
l.Descripcion2Articulo AS Description2,
l.DescripcionLinea AS DescriptionLine,
l.CodigoFamilia AS ProductGroup,
l.CodigoSubfamilia AS ProductSubGroup,
CAST(l.Unidades AS decimal(19, 6)) AS Quantity,
c.CodigoCliente AS CustomerNumber,
c.Nombre AS CustomerName,
c.CodigoNacion AS CustomerCountryCode,
c.Nacion AS CustomerCountry,
CAST(l.PrecioCoste AS decimal(19, 6)) AS StandardCost,
CAST(l.ImporteCoste AS decimal(19, 6)) AS StandardCostValue,
'EUR' AS StandardCostCurrency,
CAST(l.ImporteNeto AS decimal(19, 6)) AS SalesPriceValue,
'EUR' AS SalesCurrency,
'EUR' AS DocumentCurrency,
'EUR' AS CompanyCurrency,
c.CodigoDivisa AS SageCurrencyCode,
CAST(c.BaseImponible AS decimal(19, 6)) AS DocumentNetAmount,
CAST(c.TotalIva AS decimal(19, 6)) AS DocumentVatAmount,
CAST(c.ImporteFactura AS decimal(19, 6)) AS DocumentGrossAmount,
c.FechaFactura AS InvoiceDate,
c.FechaAlbaran AS DeliveryDate,
l.FechaRegistro AS LineRegistrationDate,
c.EjercicioPedido AS OrderYear,
c.SeriePedido AS OrderSeries,
c.NumeroPedido AS OrderNumber,
c.SuPedido AS PurchaseOrderNumber,
c.CodigoExportacion_ AS Incoterms2020,
c.CondicionExportacion_ AS IncotermsText,
c.CodigoComisionista AS SalesResponsibleEmployee,
c.StatusAbono AS CreditStatus,
c.NoFacturable AS NonBillable,
c.TipoNuevaFra AS InvoiceType,
c.StatusFacturado AS BillingStatus,
CASE
WHEN c.TipoNuevaFra = 2 OR c.SerieFactura = 'REC' OR c.StatusAbono <> 0 THEN 'Credit Note'
ELSE 'Invoice'
END AS DocumentType
FROM dbo.CabeceraAlbaranCliente c
JOIN dbo.LineasAlbaranCliente l
ON l.CodigoEmpresa = c.CodigoEmpresa
AND l.EjercicioAlbaran = c.EjercicioAlbaran
AND l.SerieAlbaran = c.SerieAlbaran
AND l.NumeroAlbaran = c.NumeroAlbaran
WHERE c.FechaFactura >= @FromDate
AND c.FechaFactura < @ToDate
ORDER BY
c.FechaFactura,
c.SerieFactura,
c.NumeroFactura,
l.Orden;
"@
$result = Export-QueryToCsv -Sql $sql -Path $csvPath
@"
Sage Spain Sales CSV export
===========================
Created: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
Server instance: $ServerInstance
Database: $Database
From date: $($FromDate.ToString("yyyy-MM-dd"))
To date: $($ToDate.ToString("yyyy-MM-dd"))
Output:
$csvPath
Rows:
$($result.Rows)
SalesPriceValue sum:
$($result.SalesPriceValueSum)
Source:
dbo.CabeceraAlbaranCliente joined with dbo.LineasAlbaranCliente
Filter:
CabeceraAlbaranCliente.FechaFactura >= FromDate
CabeceraAlbaranCliente.FechaFactura < ToDate
Notes:
- Currency is set to EUR because Sage exports EnEuros_=-1 and CodigoDivisa is empty in the analysed rows.
- SalesPriceValue uses LineasAlbaranCliente.ImporteNeto.
- DocumentNetAmount uses CabeceraAlbaranCliente.BaseImponible.
- Credit notes are marked when TipoNuevaFra=2, SerieFactura='REC', or StatusAbono is non-zero.
"@ | Set-Content -LiteralPath $summaryPath -Encoding UTF8
Write-Host "Created:"
Write-Host " $csvPath"
Write-Host " $summaryPath"
Write-Host "Rows: $($result.Rows)"
Write-Host "SalesPriceValue sum: $($result.SalesPriceValueSum)"
@@ -0,0 +1,410 @@
param(
[string]$ServerInstance = "localhost",
[string]$Database = "",
[string[]]$ObjectName = @(),
[datetime]$FromDate = "2025-01-01",
[datetime]$ToDate = "2026-01-01",
[string]$OutputDirectory = (Join-Path $env:USERPROFILE "Desktop"),
[int]$SampleRows = 500,
[int]$MaxRowsPerObject = 0,
[switch]$DiscoverOnly,
[switch]$ExportCandidates,
[switch]$IncludeSystemDatabases
)
$ErrorActionPreference = "Stop"
function New-Connection {
param([string]$DbName)
$builder = New-Object System.Data.SqlClient.SqlConnectionStringBuilder
$builder["Data Source"] = $ServerInstance
$builder["Initial Catalog"] = $DbName
$builder["Integrated Security"] = $true
$builder["TrustServerCertificate"] = $true
$builder["Connect Timeout"] = 15
return New-Object System.Data.SqlClient.SqlConnection($builder.ConnectionString)
}
function Invoke-DataTable {
param(
[string]$DbName,
[string]$Sql,
[hashtable]$Parameters = @{}
)
$conn = New-Connection $DbName
$cmd = $conn.CreateCommand()
$cmd.CommandText = $Sql
$cmd.CommandTimeout = 300
foreach ($key in $Parameters.Keys) {
$param = $cmd.Parameters.Add("@$key", [System.Data.SqlDbType]::NVarChar, 4000)
$param.Value = [string]$Parameters[$key]
}
$table = New-Object System.Data.DataTable
try {
$conn.Open()
$reader = $cmd.ExecuteReader()
$table.Load($reader)
}
finally {
$conn.Dispose()
}
return $table
}
function Convert-ToCsvValue {
param($Value)
if ($null -eq $Value -or $Value -is [System.DBNull]) {
return ""
}
if ($Value -is [datetime]) {
$text = $Value.ToString("yyyy-MM-dd HH:mm:ss")
}
else {
$text = [string]$Value
}
$text = $text.Replace('"', '""')
return '"' + $text + '"'
}
function Export-QueryToCsv {
param(
[string]$DbName,
[string]$Sql,
[string]$Path
)
$conn = New-Connection $DbName
$cmd = $conn.CreateCommand()
$cmd.CommandText = $Sql
$cmd.CommandTimeout = 0
$writer = New-Object System.IO.StreamWriter($Path, $false, [System.Text.Encoding]::UTF8)
$rowCount = 0
try {
$conn.Open()
$reader = $cmd.ExecuteReader()
$headers = for ($i = 0; $i -lt $reader.FieldCount; $i++) {
Convert-ToCsvValue $reader.GetName($i)
}
$writer.WriteLine(($headers -join ";"))
while ($reader.Read()) {
$values = for ($i = 0; $i -lt $reader.FieldCount; $i++) {
Convert-ToCsvValue $reader.GetValue($i)
}
$writer.WriteLine(($values -join ";"))
$rowCount++
}
}
finally {
$writer.Dispose()
$conn.Dispose()
}
return $rowCount
}
function Quote-NamePart {
param([string]$Name)
return "[" + $Name.Replace("]", "]]") + "]"
}
function Split-SqlObjectName {
param([string]$Name)
$parts = $Name.Split(".", 2)
if ($parts.Count -eq 1) {
return [pscustomobject]@{ SchemaName = "dbo"; ObjectName = $parts[0] }
}
return [pscustomobject]@{ SchemaName = $parts[0].Trim("[", "]"); ObjectName = $parts[1].Trim("[", "]") }
}
function Get-UserDatabases {
$sql = @"
SELECT name
FROM sys.databases
WHERE state_desc = 'ONLINE'
AND HAS_DBACCESS(name) = 1
$(if ($IncludeSystemDatabases) { "" } else { "AND database_id > 4" })
ORDER BY name;
"@
Invoke-DataTable "master" $sql | ForEach-Object { $_.name }
}
function Get-CandidateObjects {
param([string]$DbName)
$sql = @"
WITH object_columns AS (
SELECT
s.name AS SchemaName,
o.name AS ObjectName,
o.type_desc AS ObjectType,
c.name AS ColumnName,
t.name AS TypeName,
c.max_length,
c.precision,
c.scale
FROM sys.objects o
JOIN sys.schemas s ON s.schema_id = o.schema_id
JOIN sys.columns c ON c.object_id = o.object_id
JOIN sys.types t ON t.user_type_id = c.user_type_id
WHERE o.type IN ('U', 'V')
AND o.is_ms_shipped = 0
),
scored AS (
SELECT
SchemaName,
ObjectName,
ObjectType,
SUM(CASE WHEN LOWER(ObjectName) LIKE '%fact%' OR LOWER(ObjectName) LIKE '%invoice%' OR LOWER(ObjectName) LIKE '%venta%' OR LOWER(ObjectName) LIKE '%sales%' OR LOWER(ObjectName) LIKE '%albar%' OR LOWER(ObjectName) LIKE '%pedido%' THEN 5 ELSE 0 END) +
SUM(CASE WHEN LOWER(ColumnName) LIKE '%fecha%' OR LOWER(ColumnName) LIKE '%date%' THEN 2 ELSE 0 END) +
SUM(CASE WHEN LOWER(ColumnName) LIKE '%cliente%' OR LOWER(ColumnName) LIKE '%customer%' THEN 2 ELSE 0 END) +
SUM(CASE WHEN LOWER(ColumnName) LIKE '%articulo%' OR LOWER(ColumnName) LIKE '%item%' OR LOWER(ColumnName) LIKE '%producto%' THEN 2 ELSE 0 END) +
SUM(CASE WHEN LOWER(ColumnName) LIKE '%importe%' OR LOWER(ColumnName) LIKE '%neto%' OR LOWER(ColumnName) LIKE '%total%' OR LOWER(ColumnName) LIKE '%amount%' THEN 3 ELSE 0 END) +
SUM(CASE WHEN LOWER(ColumnName) LIKE '%cantidad%' OR LOWER(ColumnName) LIKE '%quantity%' OR LOWER(ColumnName) LIKE '%unidades%' THEN 2 ELSE 0 END) AS Score,
COUNT(*) AS ColumnCount,
STRING_AGG(CONVERT(nvarchar(max), ColumnName), ', ') WITHIN GROUP (ORDER BY ColumnName) AS Columns
FROM object_columns
GROUP BY SchemaName, ObjectName, ObjectType
)
SELECT TOP (80)
DB_NAME() AS DatabaseName,
SchemaName,
ObjectName,
ObjectType,
Score,
ColumnCount,
Columns
FROM scored
WHERE Score > 0
ORDER BY Score DESC, ObjectName;
"@
Invoke-DataTable $DbName $sql
}
function Get-DateColumns {
param(
[string]$DbName,
[string]$SchemaName,
[string]$ObjectNameValue
)
$sql = @"
SELECT c.name AS ColumnName
FROM sys.objects o
JOIN sys.schemas s ON s.schema_id = o.schema_id
JOIN sys.columns c ON c.object_id = o.object_id
JOIN sys.types t ON t.user_type_id = c.user_type_id
WHERE s.name = @schema
AND o.name = @object
AND (
t.name IN ('date', 'datetime', 'datetime2', 'smalldatetime')
OR LOWER(c.name) LIKE '%fecha%'
OR LOWER(c.name) LIKE '%date%'
)
ORDER BY
CASE
WHEN LOWER(c.name) LIKE '%fact%' OR LOWER(c.name) LIKE '%invoice%' THEN 0
WHEN LOWER(c.name) LIKE '%fecha%' OR LOWER(c.name) LIKE '%date%' THEN 1
ELSE 2
END,
c.column_id;
"@
Invoke-DataTable $DbName $sql @{ schema = $SchemaName; object = $ObjectNameValue } |
ForEach-Object { $_.ColumnName }
}
function Build-SelectSql {
param(
[string]$SchemaName,
[string]$ObjectNameValue,
[string]$DateColumn,
[int]$TopRows
)
$topClause = if ($TopRows -gt 0) { "TOP ($TopRows)" } else { "" }
$qualified = "$(Quote-NamePart $SchemaName).$(Quote-NamePart $ObjectNameValue)"
if ([string]::IsNullOrWhiteSpace($DateColumn)) {
return "SELECT $topClause * FROM $qualified;"
}
$from = $FromDate.ToString("yyyy-MM-dd")
$to = $ToDate.ToString("yyyy-MM-dd")
$dateColumnSql = Quote-NamePart $DateColumn
return @"
SELECT $topClause *
FROM $qualified
WHERE TRY_CONVERT(date, $dateColumnSql) >= CONVERT(date, '$from')
AND TRY_CONVERT(date, $dateColumnSql) < CONVERT(date, '$to')
ORDER BY TRY_CONVERT(date, $dateColumnSql);
"@
}
function Normalize-FileName {
param([string]$Value)
return ($Value -replace '[\\/:*?"<>|]', '_')
}
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$runDirectory = Join-Path $OutputDirectory "Sage_SQL_CSV_Export_$timestamp"
New-Item -ItemType Directory -Path $runDirectory -Force | Out-Null
$databases = if ([string]::IsNullOrWhiteSpace($Database)) {
@(Get-UserDatabases)
}
else {
@($Database)
}
$summary = New-Object System.Collections.Generic.List[object]
$allCandidates = New-Object System.Collections.Generic.List[object]
foreach ($db in $databases) {
Write-Host "Scanning database: $db"
try {
$candidates = @(Get-CandidateObjects $db)
foreach ($candidate in $candidates) {
$allCandidates.Add($candidate)
}
}
catch {
$summary.Add([pscustomobject]@{
Database = $db
Object = ""
Action = "Discovery failed"
Rows = 0
File = ""
Error = $_.Exception.Message
})
}
}
$candidatePath = Join-Path $runDirectory "candidate_objects.csv"
if ($allCandidates.Count -gt 0) {
$allCandidates | Export-Csv -LiteralPath $candidatePath -NoTypeInformation -Encoding UTF8 -Delimiter ";"
}
if (-not $DiscoverOnly) {
$objectsToExport = New-Object System.Collections.Generic.List[object]
foreach ($name in $ObjectName) {
if ([string]::IsNullOrWhiteSpace($name)) {
continue
}
if ([string]::IsNullOrWhiteSpace($Database)) {
throw "When -ObjectName is used, pass -Database as well."
}
$parsed = Split-SqlObjectName $name
$objectsToExport.Add([pscustomobject]@{
DatabaseName = $Database
SchemaName = $parsed.SchemaName
ObjectName = $parsed.ObjectName
})
}
if ($ExportCandidates) {
foreach ($candidate in ($allCandidates | Sort-Object DatabaseName, @{Expression="Score"; Descending=$true} | Select-Object -First 25)) {
$objectsToExport.Add([pscustomobject]@{
DatabaseName = $candidate.DatabaseName
SchemaName = $candidate.SchemaName
ObjectName = $candidate.ObjectName
})
}
}
foreach ($object in $objectsToExport) {
$db = $object.DatabaseName
$schema = $object.SchemaName
$objectNameValue = $object.ObjectName
try {
$dateColumn = @(Get-DateColumns $db $schema $objectNameValue | Select-Object -First 1)[0]
$limit = if ($MaxRowsPerObject -gt 0) { $MaxRowsPerObject } elseif ($ObjectName.Count -gt 0) { 0 } else { $SampleRows }
$sql = Build-SelectSql $schema $objectNameValue $dateColumn $limit
$fileName = Normalize-FileName "$db.$schema.$objectNameValue.csv"
$path = Join-Path $runDirectory $fileName
Write-Host "Exporting $db.$schema.$objectNameValue -> $path"
$rows = Export-QueryToCsv $db $sql $path
$summary.Add([pscustomobject]@{
Database = $db
Object = "$schema.$objectNameValue"
Action = "Exported"
Rows = $rows
File = $path
DateColumn = $dateColumn
Error = ""
})
}
catch {
$summary.Add([pscustomobject]@{
Database = $db
Object = "$schema.$objectNameValue"
Action = "Export failed"
Rows = 0
File = ""
DateColumn = ""
Error = $_.Exception.Message
})
}
}
}
$summaryPath = Join-Path $runDirectory "export_summary.csv"
$summary | Export-Csv -LiteralPath $summaryPath -NoTypeInformation -Encoding UTF8 -Delimiter ";"
$readmePath = Join-Path $runDirectory "README.txt"
@"
Sage SQL CSV export
===================
Server instance: $ServerInstance
Database filter: $(if ($Database) { $Database } else { "(all accessible user databases)" })
From date: $($FromDate.ToString("yyyy-MM-dd"))
To date: $($ToDate.ToString("yyyy-MM-dd"))
Files:
- candidate_objects.csv: SQL tables/views that look relevant for sales/invoices.
- export_summary.csv: export status and row counts.
- *.csv: exported samples or selected full exports.
Recommended workflow:
1. Run discovery first:
.\Export-SageSqlCsv.ps1 -DiscoverOnly
2. Send candidate_objects.csv to Trafag/IT for selection.
3. Export selected objects:
.\Export-SageSqlCsv.ps1 -Database "DATABASE_NAME" -ObjectName "schema.table_or_view"
4. If the selected object is very large, add:
-FromDate "2025-01-01" -ToDate "2026-01-01" -MaxRowsPerObject 100000
The script only reads data. It does not change SQL Server or Sage.
"@ | Set-Content -LiteralPath $readmePath -Encoding UTF8
Write-Host ""
Write-Host "Created folder:"
Write-Host " $runDirectory"
Write-Host ""
Write-Host "Main files:"
Write-Host " $candidatePath"
Write-Host " $summaryPath"
@@ -0,0 +1,334 @@
param(
[string[]]$SqlInstance = @(),
[string]$OutputDirectory = (Join-Path $env:USERPROFILE "Desktop"),
[switch]$ScanProgramFiles
)
$ErrorActionPreference = "Continue"
function Add-Section {
param([string]$Title)
$script:reportLines += ""
$script:reportLines += "============================================================"
$script:reportLines += $Title
$script:reportLines += "============================================================"
}
function Add-Line {
param([string]$Text = "")
$script:reportLines += $Text
}
function Read-RegistryValues {
param([string]$Path)
try {
if (Test-Path $Path) {
return Get-ItemProperty -Path $Path -ErrorAction Stop
}
}
catch {
Add-Line "Registry read failed: $Path :: $($_.Exception.Message)"
}
return $null
}
function Get-SageUninstallEntries {
$paths = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"
)
foreach ($path in $paths) {
Get-ItemProperty -Path $path -ErrorAction SilentlyContinue |
Where-Object {
$_.DisplayName -and (
$_.DisplayName -match "Sage" -or
$_.Publisher -match "Sage"
)
} |
Select-Object DisplayName, DisplayVersion, Publisher, InstallDate, InstallLocation, UninstallString, PSPath
}
}
function Get-SageFileVersions {
$roots = @(
$env:ProgramFiles,
${env:ProgramFiles(x86)},
$env:ProgramData
) | Where-Object { $_ -and (Test-Path $_) }
foreach ($root in $roots) {
Get-ChildItem -LiteralPath $root -Directory -ErrorAction SilentlyContinue |
Where-Object { $_.Name -match "Sage" } |
ForEach-Object {
Get-ChildItem -LiteralPath $_.FullName -Recurse -File -Include *.exe,*.dll -ErrorAction SilentlyContinue |
Where-Object { $_.VersionInfo.ProductName -match "Sage" -or $_.VersionInfo.CompanyName -match "Sage" -or $_.Name -match "Sage" } |
Select-Object FullName,
@{Name="FileVersion"; Expression={$_.VersionInfo.FileVersion}},
@{Name="ProductVersion"; Expression={$_.VersionInfo.ProductVersion}},
@{Name="ProductName"; Expression={$_.VersionInfo.ProductName}},
@{Name="CompanyName"; Expression={$_.VersionInfo.CompanyName}}
}
}
}
function Get-SqlInstancesFromRegistry {
$instanceRoots = @(
"HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Microsoft SQL Server\Instance Names\SQL"
)
$instances = New-Object System.Collections.Generic.List[object]
foreach ($root in $instanceRoots) {
$values = Read-RegistryValues $root
if (-not $values) {
continue
}
$values.PSObject.Properties |
Where-Object { $_.Name -notlike "PS*" } |
ForEach-Object {
$instanceName = $_.Name
$instanceId = [string]$_.Value
$setupPaths = @(
"HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$instanceId\Setup",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Microsoft SQL Server\$instanceId\Setup"
)
foreach ($setupPath in $setupPaths) {
$setup = Read-RegistryValues $setupPath
if ($setup) {
$instances.Add([pscustomobject]@{
InstanceName = $instanceName
InstanceId = $instanceId
Edition = $setup.Edition
Version = $setup.Version
PatchLevel = $setup.PatchLevel
ProductCode = $setup.ProductCode
SQLPath = $setup.SQLPath
SetupPath = $setupPath
})
}
}
}
}
return $instances
}
function Get-SqlServices {
Get-CimInstance Win32_Service -ErrorAction SilentlyContinue |
Where-Object { $_.Name -like "MSSQL*" -or $_.Name -like "SQLAgent*" -or $_.DisplayName -like "*SQL Server*" } |
Select-Object Name, DisplayName, State, StartMode, PathName
}
function Resolve-Sqlcmd {
$cmd = Get-Command sqlcmd.exe -ErrorAction SilentlyContinue
if ($cmd) {
return $cmd.Source
}
$candidates = @(
"$env:ProgramFiles\Microsoft SQL Server\Client SDK\ODBC\*\Tools\Binn\sqlcmd.exe",
"${env:ProgramFiles(x86)}\Microsoft SQL Server\Client SDK\ODBC\*\Tools\Binn\sqlcmd.exe",
"$env:ProgramFiles\Microsoft SQL Server\*\Tools\Binn\sqlcmd.exe",
"${env:ProgramFiles(x86)}\Microsoft SQL Server\*\Tools\Binn\sqlcmd.exe"
)
foreach ($candidate in $candidates) {
$match = Get-ChildItem -Path $candidate -ErrorAction SilentlyContinue | Select-Object -First 1
if ($match) {
return $match.FullName
}
}
return $null
}
function Invoke-SqlVersionQuery {
param(
[string]$SqlcmdPath,
[string]$Instance
)
$query = @"
SET NOCOUNT ON;
SELECT
@@VERSION AS FullVersion,
SERVERPROPERTY('ProductVersion') AS ProductVersion,
SERVERPROPERTY('ProductLevel') AS ProductLevel,
SERVERPROPERTY('Edition') AS Edition,
SERVERPROPERTY('EngineEdition') AS EngineEdition,
SERVERPROPERTY('MachineName') AS MachineName,
SERVERPROPERTY('ServerName') AS ServerName,
SERVERPROPERTY('InstanceName') AS InstanceName,
SERVERPROPERTY('Collation') AS Collation;
"@
$tempQuery = Join-Path $env:TEMP ("sql_version_query_{0}.sql" -f ([guid]::NewGuid().ToString("N")))
Set-Content -LiteralPath $tempQuery -Value $query -Encoding UTF8
try {
$output = & $SqlcmdPath -S $Instance -E -i $tempQuery -W -s "|" -b 2>&1
[pscustomobject]@{
Instance = $Instance
Success = $LASTEXITCODE -eq 0
Output = ($output -join [Environment]::NewLine)
}
}
catch {
[pscustomobject]@{
Instance = $Instance
Success = $false
Output = $_.Exception.Message
}
}
finally {
Remove-Item -LiteralPath $tempQuery -Force -ErrorAction SilentlyContinue
}
}
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
New-Item -ItemType Directory -Path $OutputDirectory -Force | Out-Null
$reportPath = Join-Path $OutputDirectory "Sage_SQL_Environment_$timestamp.txt"
$jsonPath = Join-Path $OutputDirectory "Sage_SQL_Environment_$timestamp.json"
$reportLines = New-Object System.Collections.Generic.List[string]
$computer = Get-CimInstance Win32_ComputerSystem -ErrorAction SilentlyContinue
$os = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue
$sageEntries = @(Get-SageUninstallEntries)
$sqlRegistryInstances = @(Get-SqlInstancesFromRegistry)
$sqlServices = @(Get-SqlServices)
$sageFileVersions = @()
if ($ScanProgramFiles) {
$sageFileVersions = @(Get-SageFileVersions)
}
$sqlcmdPath = Resolve-Sqlcmd
$queryInstances = New-Object System.Collections.Generic.List[string]
foreach ($instance in $SqlInstance) {
if (-not [string]::IsNullOrWhiteSpace($instance)) {
$queryInstances.Add($instance)
}
}
if ($queryInstances.Count -eq 0) {
$machineName = $env:COMPUTERNAME
foreach ($instance in $sqlRegistryInstances) {
if ($instance.InstanceName -eq "MSSQLSERVER") {
$queryInstances.Add("localhost")
}
else {
$queryInstances.Add("localhost\$($instance.InstanceName)")
}
}
}
$queryInstances = @($queryInstances | Select-Object -Unique)
$sqlQueryResults = @()
if ($sqlcmdPath -and $queryInstances.Count -gt 0) {
foreach ($instance in $queryInstances) {
$sqlQueryResults += Invoke-SqlVersionQuery -SqlcmdPath $sqlcmdPath -Instance $instance
}
}
Add-Section "Capture metadata"
Add-Line "Timestamp: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")"
Add-Line "Computer: $env:COMPUTERNAME"
Add-Line "User: $env:USERDOMAIN\$env:USERNAME"
Add-Line "Output text: $reportPath"
Add-Line "Output json: $jsonPath"
Add-Section "Windows / machine"
Add-Line "Manufacturer: $($computer.Manufacturer)"
Add-Line "Model: $($computer.Model)"
Add-Line "OS: $($os.Caption)"
Add-Line "OS Version: $($os.Version)"
Add-Line "OS Build: $($os.BuildNumber)"
Add-Line "Install date: $($os.InstallDate)"
Add-Section "Sage entries from installed programs"
if ($sageEntries.Count -eq 0) {
Add-Line "No Sage uninstall entries found."
}
else {
$sageEntries | Format-List | Out-String | ForEach-Object { Add-Line $_.TrimEnd() }
}
Add-Section "Sage file versions"
if (-not $ScanProgramFiles) {
Add-Line "Skipped. Re-run with -ScanProgramFiles for file version scan."
}
elseif ($sageFileVersions.Count -eq 0) {
Add-Line "No Sage file versions found under Program Files / ProgramData."
}
else {
$sageFileVersions | Sort-Object ProductName, ProductVersion, FullName | Format-Table -AutoSize | Out-String | ForEach-Object { Add-Line $_.TrimEnd() }
}
Add-Section "SQL Server instances from registry"
if ($sqlRegistryInstances.Count -eq 0) {
Add-Line "No SQL Server registry instances found."
}
else {
$sqlRegistryInstances | Format-List | Out-String | ForEach-Object { Add-Line $_.TrimEnd() }
}
Add-Section "SQL Server services"
if ($sqlServices.Count -eq 0) {
Add-Line "No SQL Server services found."
}
else {
$sqlServices | Format-Table -AutoSize | Out-String | ForEach-Object { Add-Line $_.TrimEnd() }
}
Add-Section "SQL Server live query"
Add-Line "sqlcmd path: $(if ($sqlcmdPath) { $sqlcmdPath } else { "not found" })"
if (-not $sqlcmdPath) {
Add-Line "Cannot query SQL Server live because sqlcmd.exe was not found."
}
elseif ($queryInstances.Count -eq 0) {
Add-Line "No SQL instances to query. Pass -SqlInstance server\instance if needed."
}
else {
foreach ($result in $sqlQueryResults) {
Add-Line ""
Add-Line "Instance: $($result.Instance)"
Add-Line "Success: $($result.Success)"
Add-Line $result.Output
}
}
$data = [pscustomobject]@{
CapturedAt = (Get-Date).ToString("o")
ComputerName = $env:COMPUTERNAME
UserName = "$env:USERDOMAIN\$env:USERNAME"
Windows = [pscustomobject]@{
Caption = $os.Caption
Version = $os.Version
BuildNumber = $os.BuildNumber
InstallDate = $os.InstallDate
}
SageUninstallEntries = $sageEntries
SageFileVersions = $sageFileVersions
SqlRegistryInstances = $sqlRegistryInstances
SqlServices = $sqlServices
SqlcmdPath = $sqlcmdPath
SqlQueryResults = $sqlQueryResults
}
$reportLines | Set-Content -LiteralPath $reportPath -Encoding UTF8
$data | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $jsonPath -Encoding UTF8
Write-Host "Created:"
Write-Host " $reportPath"
Write-Host " $jsonPath"