Public/Convert-BadgeValue.ps1
|
<#
ps-wiegand-badge - PowerShell Module to perform Wiegand Badge Conversions. Copyright (C) 2026 Robert D. Biddle This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. #> function Convert-BadgeValue { <# .SYNOPSIS Convert ValuProx 26-bit Wiegand badge data between hex, decimal Wiegand, and facility/card. .DESCRIPTION Single cmdlet with parameter sets for each conversion direction: - From facility code + card number to hex/decimal Wiegand - From hex to facility code + card number + decimal Wiegand - From decimal Wiegand to facility code + card number + hex Implements ValuProx 26-bit layout: P1 (even parity over bits 2-13), payload24 (facility 8 bits + card 16 bits), P2 (odd parity over bits 14-25). .PARAMETER HexId The hex string representation of the 26-bit Wiegand value (max 8 hex chars). .PARAMETER DecimalWiegand The decimal representation of the 26-bit Wiegand value (0 to 67108863). .PARAMETER Facility The facility code (0-255). Can be prefixed with "FC" (e.g., "FC160"). .PARAMETER BadgeNumber The badge/card number (0-65535). .PARAMETER UpperHex Output uppercase hex instead of lowercase. .PARAMETER RawHex Emit only the hex string (scripting-friendly), skipping the object wrapper. .PARAMETER IncludeBinary Add a 26-bit binary string to the output for debugging. .PARAMETER StrictParity Validate incoming parity bits and throw if they are incorrect (for FromHex/FromDecimal). .EXAMPLE Convert-BadgeValue -Facility 160 -BadgeNumber 20340 Converts facility code 160 and badge number 20340 to hex and decimal Wiegand values. .EXAMPLE Convert-BadgeValue -HexId "03409E1C" Converts a hex Wiegand value to facility code, badge number, and decimal. .EXAMPLE Convert-BadgeValue -DecimalWiegand 5456140 Converts a decimal Wiegand value to facility code, badge number, and hex. .EXAMPLE "03409E1C","03409DFD" | Convert-BadgeValue Converts multiple hex values via pipeline. .EXAMPLE Convert-BadgeValue -HexId "03409E1C" -StrictParity Converts and validates parity bits, throwing an error if they are incorrect. .OUTPUTS PSCustomObject with properties: HexId, DecimalWiegand, Facility, BadgeNumber, ParityOk, SourceParameter If -IncludeBinary is specified, also includes Word26Binary. If -RawHex is specified, returns only the hex string. .NOTES Author: Robert D. Biddle Module: ps-wiegand-badge Version: 1.0.0 The 26-bit Wiegand format is commonly used in HID proximity cards and access control systems. Bit layout: [P1][8-bit Facility][16-bit Card Number][P2] - P1: Even parity over bits 2-13 - P2: Odd parity over bits 14-25 .LINK https://github.com/RobBiddle/ps-wiegand-badge .LINK https://en.wikipedia.org/wiki/Wiegand_interface #> [CmdletBinding(DefaultParameterSetName = 'FromHex')] [Alias('cvbv')] [OutputType([PSCustomObject])] param( [Parameter(ParameterSetName = 'FromHex', Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('Hex')] [ValidatePattern('^[0-9A-Fa-f]{1,8}$')] [ValidateLength(1, 8)] [string]$HexId, [Parameter(ParameterSetName = 'FromDecimal', Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('Decimal')] [ValidateRange(0, 0x3FFFFFF)] [uint32]$DecimalWiegand, [Parameter(ParameterSetName = 'FromFacilityCard', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [string]$Facility, [Parameter(ParameterSetName = 'FromFacilityCard', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [ValidateRange(0, 0xFFFF)] [uint32]$BadgeNumber, # Output options [Parameter()] [switch]$UpperHex, [Parameter()] [switch]$RawHex, [Parameter()] [switch]$IncludeBinary, # Validation options [Parameter()] [switch]$StrictParity ) begin { # Helper function to create terminating errors with proper ErrorRecord function New-TerminatingError { param( [string]$Message, [string]$ErrorId, [System.Management.Automation.ErrorCategory]$Category = [System.Management.Automation.ErrorCategory]::InvalidArgument, [object]$TargetObject = $null ) $exception = [System.ArgumentException]::new($Message) $errorRecord = [System.Management.Automation.ErrorRecord]::new( $exception, $ErrorId, $Category, $TargetObject ) return $errorRecord } } process { switch ($PSCmdlet.ParameterSetName) { 'FromHex' { [uint32]$value26 = 0 if (-not [uint32]::TryParse($HexId, [System.Globalization.NumberStyles]::HexNumber, $null, [ref]$value26)) { $PSCmdlet.ThrowTerminatingError( (New-TerminatingError -Message "HexId '$HexId' is not valid hex." -ErrorId 'InvalidHexFormat' -TargetObject $HexId) ) } if ($value26 -gt 0x3FFFFFF) { $PSCmdlet.ThrowTerminatingError( (New-TerminatingError -Message "HexId '$HexId' exceeds 26-bit capacity (max 0x3FFFFFF / 67108863)." -ErrorId 'HexValueTooLarge' -TargetObject $HexId) ) } } 'FromDecimal' { $value26 = $DecimalWiegand if ($value26 -gt 0x3FFFFFF) { $PSCmdlet.ThrowTerminatingError( (New-TerminatingError -Message "DecimalWiegand '$DecimalWiegand' exceeds 26-bit capacity (max 67108863)." -ErrorId 'DecimalValueTooLarge' -TargetObject $DecimalWiegand) ) } } 'FromFacilityCard' { $facilityValue = ConvertTo-FacilityNumber -Text $Facility $payload24 = ([uint32]$facilityValue -shl 16) -bor ([uint32]$BadgeNumber -band 0xFFFF) $bits_2_13 = ($payload24 -shr 12) -band 0xFFF if ((Get-OnesCount -Value $bits_2_13) % 2 -eq 0) { $p1 = 0 # even parity } else { $p1 = 1 } $bits_14_25 = $payload24 -band 0xFFF if ((Get-OnesCount -Value $bits_14_25) % 2 -eq 0) { $p2 = 1 # odd parity } else { $p2 = 0 } $value26 = ($p1 -shl 25) -bor ($payload24 -shl 1) -bor $p2 } } $payload24 = ($value26 -shr 1) -band 0xFFFFFF $facilityDecoded = ($payload24 -shr 16) -band 0xFF $badgeNumberDecoded = $payload24 -band 0xFFFF # Parity verification for decoded word $actualP1 = ($value26 -shr 25) -band 0x1 $actualP2 = $value26 -band 0x1 $bits_2_13_check = ($payload24 -shr 12) -band 0xFFF $parityCount1 = (Get-OnesCount -Value $bits_2_13_check) % 2 if ($parityCount1 -eq 0) { $expectedP1 = 0 } else { $expectedP1 = 1 } $bits_14_25_check = $payload24 -band 0xFFF $parityCount2 = (Get-OnesCount -Value $bits_14_25_check) % 2 if ($parityCount2 -eq 0) { $expectedP2 = 1 } else { $expectedP2 = 0 } $ParityOk = ($actualP1 -eq $expectedP1) -and ($actualP2 -eq $expectedP2) if ($StrictParity -and -not $ParityOk) { $parityLabel = if ($PSCmdlet.ParameterSetName -eq 'FromHex') { $HexId } elseif ($PSCmdlet.ParameterSetName -eq 'FromDecimal') { $DecimalWiegand } else { 'payload' } $PSCmdlet.ThrowTerminatingError( (New-TerminatingError -Message "Parity check failed for value '$parityLabel'." -ErrorId 'ParityCheckFailed' -Category ([System.Management.Automation.ErrorCategory]::InvalidData) -TargetObject $parityLabel) ) } $hexOut = ('{0:x8}' -f $value26) if ($UpperHex) { $hexOut = $hexOut.ToUpperInvariant() } if ($RawHex) { return $hexOut } $result = [ordered]@{ PSTypeName = 'WiegandBadge' HexId = $hexOut DecimalWiegand = $value26 Facility = $facilityDecoded BadgeNumber = $badgeNumberDecoded ParityOk = $ParityOk SourceParameter = $PSCmdlet.ParameterSetName } if ($IncludeBinary) { $result.Word26Binary = [Convert]::ToString($value26, 2).PadLeft(26, '0') } [PSCustomObject]$result } } |