param( [string]$HostName = "localhost", [UInt16]$Port = 32568, [string]$TagName = $env:HISTORIAN_TEST_TAG, [int]$LookbackMinutes = 60, [int]$MaxRows = 5, [int]$ConnectionWaitSeconds = 15, [int]$PreReadSleepSeconds = 0, [switch]$DumpLoadedModules ) $ErrorActionPreference = "Stop" function Set-PropertyIfPresent { param( [object]$Object, [string]$Name, [object]$Value ) $property = $Object.GetType().GetProperty($Name) if ($null -ne $property -and $property.CanWrite) { $property.SetValue($Object, $Value, $null) } } function Get-PropertyValue { param( [object]$Object, [string]$Name ) if ($null -eq $Object) { return $null } $property = $Object.GetType().GetProperty($Name) if ($null -eq $property -or -not $property.CanRead) { return $null } return $property.GetValue($Object, $null) } function Get-PropertyText { param( [object]$Object, [string]$Name ) $value = Get-PropertyValue $Object $Name if ($null -eq $value) { return $null } return $value.ToString() } function Invoke-ByRefMethod { param( [object]$Target, [Reflection.MethodInfo]$Method, [object[]]$Arguments ) return $Method.Invoke($Target, $Arguments) } if ([string]::IsNullOrWhiteSpace($TagName)) { $TagName = Read-Host "Historian test tag" } if ([string]::IsNullOrWhiteSpace($TagName)) { throw "A tag name is required. Pass -TagName or set HISTORIAN_TEST_TAG." } $repoRoot = Split-Path -Parent $PSScriptRoot $dllDir = Join-Path $repoRoot "current" $managedDll = Join-Path $dllDir "aahClientManaged.dll" if (-not (Test-Path -LiteralPath $managedDll)) { throw "Missing aahClientManaged.dll at $managedDll" } [AppDomain]::CurrentDomain.add_AssemblyResolve({ param($sender, $eventArgs) $assemblyName = New-Object Reflection.AssemblyName($eventArgs.Name) $candidate = Join-Path $script:dllDir ($assemblyName.Name + ".dll") if (Test-Path -LiteralPath $candidate) { return [Reflection.Assembly]::LoadFrom($candidate) } return $null }) Push-Location $dllDir try { $assembly = [Reflection.Assembly]::LoadFrom($managedDll) if ($DumpLoadedModules) { [Diagnostics.Process]::GetCurrentProcess().Modules | Where-Object { $_.ModuleName -like '*aah*' -or $_.FileName -like '*histsdk*' -or $_.FileName -like '*AVEVA*' } | Select-Object ModuleName, BaseAddress, ModuleMemorySize, FileName | ConvertTo-Json -Depth 3 } if ($PreReadSleepSeconds -gt 0) { Start-Sleep -Seconds $PreReadSleepSeconds } $accessType = $assembly.GetType("ArchestrA.HistorianAccess", $true) $connectionArgsType = $assembly.GetType("ArchestrA.HistorianConnectionArgs", $true) $connectionStatusType = $assembly.GetType("ArchestrA.HistorianConnectionStatus", $true) $connectionType = $assembly.GetType("ArchestrA.HistorianConnectionType", $true) $historyQueryArgsType = $assembly.GetType("ArchestrA.HistoryQueryArgs", $true) $errorType = $assembly.GetType("ArchestrA.HistorianAccessError", $true) $retrievalModeType = $assembly.GetType("ArchestrA.HistorianRetrievalMode", $true) $access = [Activator]::CreateInstance($accessType) $connectionArgs = [Activator]::CreateInstance($connectionArgsType) Set-PropertyIfPresent $connectionArgs "ServerName" $HostName Set-PropertyIfPresent $connectionArgs "TcpPort" $Port Set-PropertyIfPresent $connectionArgs "ReadOnly" $true Set-PropertyIfPresent $connectionArgs "IntegratedSecurity" $true Set-PropertyIfPresent $connectionArgs "ConnectionType" ([Enum]::Parse($connectionType, "Process")) $openError = [Activator]::CreateInstance($errorType) $openMethod = $accessType.GetMethod("OpenConnection", [Type[]]@($connectionArgsType, $errorType.MakeByRefType())) $openArgs = New-Object "object[]" 2 $openArgs[0] = $connectionArgs $openArgs[1] = $openError $openSuccess = [bool](Invoke-ByRefMethod $access $openMethod $openArgs) $openError = $openArgs[1] $connected = $false $pending = $false $connectionErrorOccurred = $false $connectionStatusError = $null $getStatusMethod = $accessType.GetMethod("GetConnectionStatus", [Type[]]@($connectionStatusType.MakeByRefType())) $deadline = [DateTime]::UtcNow.AddSeconds([Math]::Max($ConnectionWaitSeconds, 1)) do { $status = [Activator]::CreateInstance($connectionStatusType) $statusArgs = New-Object "object[]" 1 $statusArgs[0] = $status [void](Invoke-ByRefMethod $access $getStatusMethod $statusArgs) $status = $statusArgs[0] $connected = [bool](Get-PropertyValue $status "ConnectedToServer") $pending = [bool](Get-PropertyValue $status "Pending") $connectionErrorOccurred = [bool](Get-PropertyValue $status "ErrorOccurred") $connectionStatusError = Get-PropertyValue $status "Error" if (($connected -and -not $pending) -or $connectionErrorOccurred -or (-not $connected -and -not $pending)) { break } Start-Sleep -Milliseconds 250 } while ([DateTime]::UtcNow -lt $deadline) $rows = New-Object System.Collections.Generic.List[object] $startSuccess = $false $moveErrorText = $null $startError = $null if ($openSuccess -and $connected) { $query = $accessType.GetMethod("CreateHistoryQuery", [Type[]]@()).Invoke($access, @()) $queryType = $query.GetType() $queryArgs = [Activator]::CreateInstance($historyQueryArgsType) $tags = New-Object System.Collections.Specialized.StringCollection [void]$tags.Add($TagName) Set-PropertyIfPresent $queryArgs "TagNames" $tags Set-PropertyIfPresent $queryArgs "StartDateTime" ([DateTime]::Now.AddMinutes(-1 * $LookbackMinutes)) Set-PropertyIfPresent $queryArgs "EndDateTime" ([DateTime]::Now) Set-PropertyIfPresent $queryArgs "BatchSize" ([uint32][Math]::Max($MaxRows, 1)) Set-PropertyIfPresent $queryArgs "RetrievalMode" ([Enum]::Parse($retrievalModeType, "Full")) $queryError = [Activator]::CreateInstance($errorType) $startMethod = $queryType.GetMethod("StartQuery", [Type[]]@($historyQueryArgsType, $errorType.MakeByRefType())) $startArgs = New-Object "object[]" 2 $startArgs[0] = $queryArgs $startArgs[1] = $queryError $startSuccess = [bool](Invoke-ByRefMethod $query $startMethod $startArgs) $startError = $startArgs[1] if ($startSuccess) { $moveMethod = $queryType.GetMethod("MoveNext", [Type[]]@($errorType.MakeByRefType())) for ($i = 0; $i -lt $MaxRows; $i++) { $moveError = [Activator]::CreateInstance($errorType) $moveArgs = New-Object "object[]" 1 $moveArgs[0] = $moveError $hasRow = [bool](Invoke-ByRefMethod $query $moveMethod $moveArgs) $moveError = $moveArgs[0] if (-not $hasRow) { $moveErrorText = Get-PropertyText $moveError "ErrorDescription" break } $result = Get-PropertyValue $query "QueryResult" $row = [ordered]@{ StartDateTime = (Get-PropertyValue $result "StartDateTime").ToString("O") EndDateTime = (Get-PropertyValue $result "EndDateTime").ToString("O") Quality = Get-PropertyValue $result "Quality" OpcQuality = Get-PropertyValue $result "OpcQuality" QualityDetail = Get-PropertyValue $result "QualityDetail" Value = Get-PropertyValue $result "Value" StringValuePresent = -not [string]::IsNullOrEmpty((Get-PropertyText $result "StringValue")) PercentGood = Get-PropertyValue $result "PercentGood" } $rows.Add($row) } } $endMethod = $queryType.GetMethod("EndQuery", [Type[]]@($errorType.MakeByRefType())) if ($null -ne $endMethod) { $endError = [Activator]::CreateInstance($errorType) $endArgs = New-Object "object[]" 1 $endArgs[0] = $endError [void](Invoke-ByRefMethod $query $endMethod $endArgs) } $queryDispose = $query -as [IDisposable] if ($null -ne $queryDispose) { $queryDispose.Dispose() } } $resultObject = [ordered]@{ Operation = "aahClientManaged.HistoryQuery.Integrated" HostName = $HostName Port = $Port IntegratedSecurity = $true TagNameProvided = $true LookbackMinutes = $LookbackMinutes OpenSuccess = $openSuccess OpenErrorType = Get-PropertyText $openError "ErrorType" OpenErrorCode = Get-PropertyText $openError "ErrorCode" OpenErrorDescription = Get-PropertyText $openError "ErrorDescription" ConnectedToServer = $connected ConnectionPending = $pending ConnectionErrorOccurred = $connectionErrorOccurred ConnectionStatusErrorType = Get-PropertyText $connectionStatusError "ErrorType" ConnectionStatusErrorCode = Get-PropertyText $connectionStatusError "ErrorCode" ConnectionStatusErrorDescription = Get-PropertyText $connectionStatusError "ErrorDescription" StartQuerySuccess = $startSuccess StartQueryErrorType = Get-PropertyText $startError "ErrorType" StartQueryErrorCode = Get-PropertyText $startError "ErrorCode" StartQueryErrorDescription = Get-PropertyText $startError "ErrorDescription" MoveTerminalDescription = $moveErrorText RowCount = $rows.Count Rows = $rows } $resultObject | ConvertTo-Json -Depth 6 if ($DumpLoadedModules) { [Diagnostics.Process]::GetCurrentProcess().Modules | Where-Object { $_.ModuleName -like '*aah*' -or $_.FileName -like '*histsdk*' -or $_.FileName -like '*AVEVA*' } | Select-Object ModuleName, BaseAddress, ModuleMemorySize, FileName | ConvertTo-Json -Depth 3 } if ($openSuccess) { $closeError = [Activator]::CreateInstance($errorType) $closeMethod = $accessType.GetMethod("CloseConnection", [Type[]]@($errorType.MakeByRefType())) if ($null -ne $closeMethod) { $closeArgs = New-Object "object[]" 1 $closeArgs[0] = $closeError [void](Invoke-ByRefMethod $access $closeMethod $closeArgs) } } $dispose = $access -as [IDisposable] if ($null -ne $dispose) { $dispose.Dispose() } } finally { Pop-Location }