├── module └── hl7tools │ ├── hl7tools.psd1 │ └── hl7tools.format.ps1xml ├── help ├── README ├── hl7tools.md ├── Show-HL7MessageTimeline.md ├── Receive-HL7Message.md ├── Split-HL7BatchFile.md ├── Remove-HL7Identifiers.md ├── Select-HL7Item.md ├── Remove-HL7Item.md ├── Send-HL7Message.md └── Set-HL7Item.md ├── publish.cmd ├── omnisharp.json ├── .vscode ├── tasks.json └── launch.json ├── .github └── workflows │ ├── Build solution on push.yml │ ├── Publish release to PowerShell Gallery.yml │ └── codeql.yml ├── ChangeLog.txt ├── LICENSE ├── hl7tools.csproj ├── src ├── ReceivedMessageResult.cs ├── Common.cs ├── ReceiveHL7Message.cs ├── RemoveHL7Identifiers.cs ├── SelectHL7Item.cs ├── SplitHL7BatchFile.cs ├── ShowHL7MessageTimeline.cs ├── HL7TcpListener.cs ├── RemoveHL7Item.cs ├── SetHL7Item.cs └── SendHL7Message.cs ├── .gitattributes ├── .gitignore └── install.ps1 /module/hl7tools/hl7tools.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobHolme/HL7-Powershell-Module/HEAD/module/hl7tools/hl7tools.psd1 -------------------------------------------------------------------------------- /help/README: -------------------------------------------------------------------------------- 1 | Help generated using the PlatyPS module: 2 | 3 | https://docs.microsoft.com/en-gb/powershell/scripting/dev-cross-plat/create-help-using-platyps?view=powershell-7.2 4 | -------------------------------------------------------------------------------- /publish.cmd: -------------------------------------------------------------------------------- 1 | del .\module\hl7tools\lib\ 2 | dotnet publish --configuration release --framework net6.0 --output .\module\hl7tools\lib\net6.0\ 3 | dotnet publish --configuration release --framework net48 --output .\module\hl7tools\lib\net48\ 4 | -------------------------------------------------------------------------------- /omnisharp.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "FormattingOptions": { 4 | "NewLinesForBracesInLambdaExpressionBody": false, 5 | "NewLinesForBracesInAnonymousMethods": false, 6 | "NewLinesForBracesInAnonymousTypes": false, 7 | "NewLinesForBracesInControlBlocks": false, 8 | "NewLinesForBracesInTypes": false, 9 | "NewLinesForBracesInMethods": false, 10 | "NewLinesForBracesInProperties": false, 11 | "NewLinesForBracesInObjectCollectionArrayInitializers": false, 12 | "NewLinesForBracesInAccessors": false, 13 | "NewLineForElse": true, 14 | "NewLineForCatch": true, 15 | "NewLineForFinally": true 16 | } 17 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "command": "dotnet", 9 | "type": "shell", 10 | "args": [ 11 | "build", 12 | // Ask dotnet build to generate full paths for file names. 13 | "/property:GenerateFullPaths=true", 14 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel 15 | "/consoleloggerparameters:NoSummary" 16 | ], 17 | "group": "build", 18 | "presentation": { 19 | "reveal": "silent" 20 | }, 21 | "problemMatcher": "$msCompile" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "PowerShell cmdlets: pwsh", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "program": "pwsh", 12 | "args": [ 13 | "-NoExit", 14 | "-NoProfile", 15 | "-Command", 16 | "Import-Module '${workspaceFolder}/module/DicomTools.psd1'", 17 | ], 18 | "cwd": "${workspaceFolder}", 19 | "stopAtEntry": false, 20 | "console": "integratedTerminal" 21 | }, 22 | { 23 | "name": ".NET Core Attach", 24 | "type": "coreclr", 25 | "request": "attach" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /.github/workflows/Build solution on push.yml: -------------------------------------------------------------------------------- 1 | name: Build solution on push 2 | 3 | on: 4 | # triggered on push to main branch 5 | push: 6 | branches: [ "master" ] 7 | pull_request: 8 | branches: [ "master" ] 9 | # manual trigger 10 | workflow_dispatch: 11 | 12 | jobs: 13 | publish: 14 | runs-on: windows-latest 15 | 16 | steps: 17 | # checkout repo 18 | - name: Checkout repo 19 | uses: actions/checkout@v4 20 | 21 | # build .Net Core solution 22 | - name: Build .net core 6.0 solution 23 | run: dotnet publish --configuration release --framework net6.0 --output .\module\hl7tools\lib\net6.0\ 24 | 25 | # build .Net Framework solution 26 | - name: Build .net framework 4.8 solution 27 | run: dotnet publish --configuration release --framework net48 --output .\module\hl7tools\lib\net48\ -------------------------------------------------------------------------------- /ChangeLog.txt: -------------------------------------------------------------------------------- 1 | 2 | v1.7.5 (18/09/2021) - Added -UseTLS switch to support connections to endpoints requiring TLS. 3 | v1.7.6 (26/09/2021) - Supports a list of values for the -ItemPosition parameter. 4 | v1.7.7 (01/10/2021) - merged NetFramework and NetStandard builds into single project. No chang to functionality. 5 | v1.7.8 (17/07/2022) - Added -SkipCertificateCheck to ignore TLS certificate errors. 6 | v1.7.10 (28/08/2022) - Updated build to use .net framework 4.8 7 | v1.7.11 (03/09/2022) - Select-HL7Item now returns an empty value for items not found in the message. This predominately applies to repeating items, such as repeating segments. 8 | Now the number of values returned should equal the number of segments present in the message. Previously non existant items were omitted from the array of results returned. 9 | v1.7.12 (12/03/2023) - Update build to target .Net 6.0 instead of .Net Standard 2.0. No functional changes. 10 | v1.7.13 (22/03/2023) - Added option to gracefully exit Receive-HL7Message 11 | v1.7.14 (26/02/2025) - Added -MessageString parameter to Send-HL7Message. This accepts the message as a string, instead of a file. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Rob Holme 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /hl7tools.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0;net48 5 | true 6 | x64 7 | Rob Holme 8 | HL7Tools PowerShell Module 9 | Powershell module for working with HL7 2.x files 10 | 1.7.14 11 | 1.7.14.0 12 | https://github.com/RobHolme/HL7-Powershell-Module 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | False 23 | C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.github/workflows/Publish release to PowerShell Gallery.yml: -------------------------------------------------------------------------------- 1 | name: Publish release to PowerShell Gallery 2 | on: 3 | # triggered on new release (ignores draft releases) 4 | release: 5 | types: [published] 6 | # manual trigger 7 | workflow_dispatch: 8 | 9 | jobs: 10 | publish: 11 | runs-on: windows-latest 12 | 13 | steps: 14 | # checkout repo 15 | - name: Checkout repo 16 | uses: actions/checkout@v4 17 | 18 | # build .Net Core solution 19 | - name: Build .net core 6.0 solution 20 | run: dotnet publish --configuration release --framework net6.0 --output .\module\hl7tools\lib\net6.0\ 21 | 22 | # build .Net Framework solution 23 | - name: Build .net framework 4.8 solution 24 | run: dotnet publish --configuration release --framework net48 --output .\module\hl7tools\lib\net48\ 25 | 26 | # publish to powershellgallery.com 27 | # secrets.PS_GALLERY_KEY is the API key for the PowerShell Gallery. 28 | # The key may need updating in the secrets section of the repository settings if expired. 29 | - name: Publish module 30 | env: 31 | NUGET_KEY: ${{ secrets.PS_GALLERY_KEY }} 32 | shell: pwsh 33 | run: Publish-Module -path .\module\hl7tools -NuGetApiKey $env:NUGET_KEY -Verbose 34 | -------------------------------------------------------------------------------- /help/hl7tools.md: -------------------------------------------------------------------------------- 1 | --- 2 | Module Name: HL7tools 3 | Module Guid: d5c6a068-96fd-49cb-aa72-92c6e3d090f2 4 | Download Help Link: {{ Update Download Link }} 5 | Help Version: {{ Please enter version of help manually (X.X.X.X) format }} 6 | Locale: en-US 7 | --- 8 | 9 | # HL7tools Module 10 | ## Description 11 | {{ Fill in the Description }} 12 | 13 | ## HL7tools Cmdlets 14 | ### [Receive-HL7Message](Receive-HL7Message.md) 15 | Receive HL7 v2.x messages via a TCP connection (MLLP). 16 | 17 | ### [Remove-HL7Identifiers](Remove-HL7Identifiers.md) 18 | Remove personal identifiers for patients and next of kin from HL7 files. 19 | 20 | ### [Remove-HL7Item](Remove-HL7Item.md) 21 | Delete the value for a nominated item in the HL7 message. 22 | 23 | ### [Select-HL7Item](Select-HL7Item.md) 24 | Queries a HL7 file (or list of files) to return a specific HL7 Item. 25 | 26 | ### [Send-HL7Message](Send-HL7Message.md) 27 | Send a HL7 file to a remote host via TCP (MLLP). 28 | 29 | ### [Set-HL7Item](Set-HL7Item.md) 30 | Sets the value of an existing item within a HL7 file. 31 | 32 | ### [Show-HL7MessageTimeline](Show-HL7MessageTimeline.md) 33 | List messages chronologically based on the header timestamp (MSH-7). 34 | 35 | ### [Split-HL7BatchFile](Split-HL7BatchFile.md) 36 | Split a batch file containing multiple messages into a separate file per message. 37 | 38 | -------------------------------------------------------------------------------- /src/ReceivedMessageResult.cs: -------------------------------------------------------------------------------- 1 | namespace HL7Tools 2 | { 3 | /// 4 | /// Simple class to store the result of the received HL7 message. This ibject is queued from the recieving thread to the CmdLet to output the object to the powershell pipeline. 5 | /// 6 | class ReceivedMessageResult 7 | { 8 | private string trigger; 9 | private string fileName; 10 | private string remoteConnection; 11 | 12 | /// 13 | /// Constructor 14 | /// 15 | public ReceivedMessageResult(string MessageTrigger, string MessageFilename, string MessageRemoteConnection) 16 | { 17 | this.trigger = MessageTrigger; 18 | this.fileName = MessageFilename; 19 | this.remoteConnection = MessageRemoteConnection; 20 | } 21 | 22 | /// 23 | /// 24 | /// 25 | public string Trigger 26 | { 27 | set { this.trigger = value; } 28 | get { return this.trigger; } 29 | } 30 | 31 | /// 32 | /// 33 | /// 34 | public string Filename 35 | { 36 | set { this.fileName = value; } 37 | get { return this.fileName; } 38 | } 39 | 40 | /// 41 | /// 42 | /// 43 | public string RemoteConnection 44 | { 45 | set { this.remoteConnection = value; } 46 | get { return this.remoteConnection; } 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | 21 | jobs: 22 | analyze: 23 | name: Analyze 24 | runs-on: windows-latest 25 | permissions: 26 | actions: read 27 | contents: read 28 | security-events: write 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | language: [ 'csharp' ] 34 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 35 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 36 | 37 | steps: 38 | - name: Checkout repository 39 | uses: actions/checkout@v3 40 | 41 | # Initializes the CodeQL tools for scanning. 42 | - name: Initialize CodeQL 43 | uses: github/codeql-action/init@v2 44 | with: 45 | languages: ${{ matrix.language }} 46 | # If you wish to specify custom queries, you can do so here or in a config file. 47 | # By default, queries listed here will override any specified in a config file. 48 | # Prefix the list here with "+" to use these queries and those in the config file. 49 | 50 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 51 | # queries: security-extended,security-and-quality 52 | 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v2 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 61 | 62 | # If the Autobuild fails above, remove it and uncomment the following three lines. 63 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 64 | 65 | # - run: | 66 | # echo "Run, Build Application using script" 67 | # ./location_of_script_within_repo/buildscript.sh 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v2 71 | with: 72 | category: "/language:${{matrix.language}}" 73 | -------------------------------------------------------------------------------- /help/Show-HL7MessageTimeline.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: hl7tools.dll-Help.xml 3 | Module Name: hl7tools 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Show-HL7MessageTimeline 9 | 10 | ## SYNOPSIS 11 | List messages chronologically based on the header timestamp (MSH-7). 12 | 13 | ## SYNTAX 14 | 15 | ### Literal 16 | ``` 17 | Show-HL7MessageTimeline -LiteralPath [-Descending] [-ProgressAction ] 18 | [] 19 | ``` 20 | 21 | ### Path 22 | ``` 23 | Show-HL7MessageTimeline [-Path] [-Descending] [-ProgressAction ] 24 | [] 25 | ``` 26 | 27 | ## DESCRIPTION 28 | Lists messages chronologically based on the message header receive date/time field (MSH-7). 29 | 30 | ## EXAMPLES 31 | 32 | ### Example 1 33 | ```powershell 34 | PS C:\> {{ Add example code here }} 35 | ``` 36 | 37 | {{ Add example description here }} 38 | 39 | ## PARAMETERS 40 | 41 | ### -Descending 42 | Switch to show messages in descending chronological order (defaults to ascending without this switch). 43 | 44 | ```yaml 45 | Type: SwitchParameter 46 | Parameter Sets: (All) 47 | Aliases: Desc 48 | 49 | Required: False 50 | Position: Named 51 | Default value: None 52 | Accept pipeline input: False 53 | Accept wildcard characters: False 54 | ``` 55 | 56 | ### -LiteralPath 57 | {{ Fill LiteralPath Description }} 58 | 59 | ```yaml 60 | Type: String[] 61 | Parameter Sets: Literal 62 | Aliases: PSPath, Name, Filename 63 | 64 | Required: True 65 | Position: Named 66 | Default value: None 67 | Accept pipeline input: True (ByPropertyName) 68 | Accept wildcard characters: False 69 | ``` 70 | 71 | ### -Path 72 | The full or relative path a single HL7 file or directory. 73 | This may include wildcards in the path name. 74 | If a directory is provided, all files within the directory will be examined. 75 | Exceptions will be raised if a file isn't identified as a HL7 v2.x file. 76 | This parameter accepts a list of files, separate each file file with a ','. 77 | 78 | 79 | ```yaml 80 | Type: String[] 81 | Parameter Sets: Path 82 | Aliases: 83 | 84 | Required: True 85 | Position: 0 86 | Default value: None 87 | Accept pipeline input: True (ByPropertyName, ByValue) 88 | Accept wildcard characters: False 89 | ``` 90 | 91 | ### -ProgressAction 92 | {{ Fill ProgressAction Description }} 93 | 94 | ```yaml 95 | Type: ActionPreference 96 | Parameter Sets: (All) 97 | Aliases: proga 98 | 99 | Required: False 100 | Position: Named 101 | Default value: None 102 | Accept pipeline input: False 103 | Accept wildcard characters: False 104 | ``` 105 | 106 | ### CommonParameters 107 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 108 | 109 | ## INPUTS 110 | 111 | ### System.String[] 112 | 113 | ## OUTPUTS 114 | 115 | ### System.Object 116 | ## NOTES 117 | 118 | ## RELATED LINKS 119 | 120 | [Online Help](https://github.com/RobHolme/HL7-Powershell-Module#show-hl7messagetimeline) 121 | -------------------------------------------------------------------------------- /help/Receive-HL7Message.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: hl7tools.dll-Help.xml 3 | Module Name: hl7tools 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Receive-HL7Message 9 | 10 | ## SYNOPSIS 11 | Receive HL7 v2.x messages via a TCP connection (MLLP). 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Receive-HL7Message [-Path] [-Port] [[-Timeout] ] [[-Encoding] ] [-NoACK] 17 | [-ProgressAction ] [] 18 | ``` 19 | 20 | ## DESCRIPTION 21 | Receives a HL7 v2.x message via a TCP connection (MLLP framing). Pres 'esc' to close the listener. 22 | 23 | ## EXAMPLES 24 | 25 | ### Example 1 26 | ```powershell 27 | PS C:\> Receive-HL7Message -Path c:\hl7 -Port 5000 28 | ``` 29 | 30 | Start a MLLP listener on port 5000. Save received files to c:\hl7\ 31 | 32 | ## PARAMETERS 33 | 34 | ### -Encoding 35 | Set the text encoding. 36 | Supports "UTF-8" or "ISO-8859-1" (Western European). 37 | Defaults to "UTF-8" if parameter not supplied. 38 | 39 | ```yaml 40 | Type: String 41 | Parameter Sets: (All) 42 | Aliases: 43 | Accepted values: UTF-8, ISO-8859-1 44 | 45 | Required: False 46 | Position: 3 47 | Default value: None 48 | Accept pipeline input: False 49 | Accept wildcard characters: False 50 | ``` 51 | 52 | ### -NoACK 53 | Do not send an acknowledgement (ACK) message. 54 | 55 | ```yaml 56 | Type: SwitchParameter 57 | Parameter Sets: (All) 58 | Aliases: 59 | 60 | Required: False 61 | Position: Named 62 | Default value: None 63 | Accept pipeline input: False 64 | Accept wildcard characters: False 65 | ``` 66 | 67 | ### -Path 68 | The path to save received messages to. 69 | 70 | ```yaml 71 | Type: String 72 | Parameter Sets: (All) 73 | Aliases: 74 | 75 | Required: True 76 | Position: 0 77 | Default value: None 78 | Accept pipeline input: False 79 | Accept wildcard characters: False 80 | ``` 81 | 82 | ### -Port 83 | The TCP port to listen on. 84 | 85 | ```yaml 86 | Type: Int32 87 | Parameter Sets: (All) 88 | Aliases: 89 | 90 | Required: True 91 | Position: 1 92 | Default value: None 93 | Accept pipeline input: False 94 | Accept wildcard characters: False 95 | ``` 96 | 97 | ### -Timeout 98 | The timeout to end idle TCP connections in seconds. 99 | 100 | ```yaml 101 | Type: Int32 102 | Parameter Sets: (All) 103 | Aliases: 104 | 105 | Required: False 106 | Position: 2 107 | Default value: None 108 | Accept pipeline input: False 109 | Accept wildcard characters: False 110 | ``` 111 | 112 | ### -ProgressAction 113 | {{ Fill ProgressAction Description }} 114 | 115 | ```yaml 116 | Type: ActionPreference 117 | Parameter Sets: (All) 118 | Aliases: proga 119 | 120 | Required: False 121 | Position: Named 122 | Default value: None 123 | Accept pipeline input: False 124 | Accept wildcard characters: False 125 | ``` 126 | 127 | ### CommonParameters 128 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 129 | 130 | ## INPUTS 131 | 132 | ### None 133 | 134 | ## OUTPUTS 135 | 136 | ### System.Object 137 | ## NOTES 138 | 139 | ## RELATED LINKS 140 | 141 | [Online Help](https://github.com/RobHolme/HL7-Powershell-Module#receive-hl7message) -------------------------------------------------------------------------------- /help/Split-HL7BatchFile.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: hl7tools.dll-Help.xml 3 | Module Name: hl7tools 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Split-HL7BatchFile 9 | 10 | ## SYNOPSIS 11 | Split a batch file containing multiple messages into a separate file per message. 12 | 13 | ## SYNTAX 14 | 15 | ### Literal 16 | ``` 17 | Split-HL7BatchFile -LiteralPath [-OverwriteFile] [-ProgressAction ] [-WhatIf] 18 | [-Confirm] [] 19 | ``` 20 | 21 | ### Path 22 | ``` 23 | Split-HL7BatchFile [-Path] [-OverwriteFile] [-ProgressAction ] [-WhatIf] 24 | [-Confirm] [] 25 | ``` 26 | 27 | ## DESCRIPTION 28 | Splits a HL7 batch file into a separate file per HL7 message. 29 | 30 | ## EXAMPLES 31 | 32 | ### Example 1 33 | ```powershell 34 | PS C:\> Split-HL7BatchFile -Path c:\test\batch.hl7 35 | ``` 36 | 37 | ## PARAMETERS 38 | 39 | ### -Confirm 40 | Prompts you for confirmation before running the cmdlet. 41 | 42 | ```yaml 43 | Type: SwitchParameter 44 | Parameter Sets: (All) 45 | Aliases: cf 46 | 47 | Required: False 48 | Position: Named 49 | Default value: None 50 | Accept pipeline input: False 51 | Accept wildcard characters: False 52 | ``` 53 | 54 | ### -LiteralPath 55 | Same as -Path, only wildcards are not expanded. 56 | Use this if the literal path includes a wildcard character you do not intent to expand. 57 | 58 | ```yaml 59 | Type: String[] 60 | Parameter Sets: Literal 61 | Aliases: PSPath, Name, Filename, Fullname 62 | 63 | Required: True 64 | Position: Named 65 | Default value: None 66 | Accept pipeline input: True (ByPropertyName, ByValue) 67 | Accept wildcard characters: False 68 | ``` 69 | 70 | ### -OverwriteFile 71 | Switch to suppress warnings when overwriting existing files. 72 | 73 | ```yaml 74 | Type: SwitchParameter 75 | Parameter Sets: (All) 76 | Aliases: Overwrite, Force 77 | 78 | Required: False 79 | Position: Named 80 | Default value: None 81 | Accept pipeline input: False 82 | Accept wildcard characters: False 83 | ``` 84 | 85 | ### -Path 86 | The full or relative path a single HL7 file or directory. 87 | This may include wildcards in the path name. 88 | If a directory is provide, all files within the directory will be examined. 89 | Exceptions will be raised if a file isn't identified as a HL7 v2.x file. 90 | This parameter accepts a list of files, separate each file file with a ','. 91 | 92 | ```yaml 93 | Type: String[] 94 | Parameter Sets: Path 95 | Aliases: 96 | 97 | Required: True 98 | Position: 0 99 | Default value: None 100 | Accept pipeline input: True (ByPropertyName, ByValue) 101 | Accept wildcard characters: False 102 | ``` 103 | 104 | ### -WhatIf 105 | Shows what would happen if the cmdlet runs. 106 | The cmdlet is not run. 107 | 108 | ```yaml 109 | Type: SwitchParameter 110 | Parameter Sets: (All) 111 | Aliases: wi 112 | 113 | Required: False 114 | Position: Named 115 | Default value: None 116 | Accept pipeline input: False 117 | Accept wildcard characters: False 118 | ``` 119 | 120 | ### -ProgressAction 121 | {{ Fill ProgressAction Description }} 122 | 123 | ```yaml 124 | Type: ActionPreference 125 | Parameter Sets: (All) 126 | Aliases: proga 127 | 128 | Required: False 129 | Position: Named 130 | Default value: None 131 | Accept pipeline input: False 132 | Accept wildcard characters: False 133 | ``` 134 | 135 | ### CommonParameters 136 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 137 | 138 | ## INPUTS 139 | 140 | ### System.String[] 141 | 142 | ## OUTPUTS 143 | 144 | ### System.Object 145 | ## NOTES 146 | 147 | ## RELATED LINKS 148 | 149 | [Online Help](https://github.com/RobHolme/HL7-Powershell-Module#split-hl7batchfile) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | hl7-powershell-module.sln 10 | 11 | # User-specific files (MonoDevelop/Xamarin Studio) 12 | *.userprefs 13 | 14 | # Build results 15 | [Dd]ebug/ 16 | [Dd]ebugPublic/ 17 | [Rr]elease/ 18 | [Rr]eleases/ 19 | x64/ 20 | x86/ 21 | build/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | module/hl7tools/lib/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | 88 | # TFS 2012 Local Workspace 89 | $tf/ 90 | 91 | # Guidance Automation Toolkit 92 | *.gpState 93 | 94 | # ReSharper is a .NET coding add-in 95 | _ReSharper*/ 96 | *.[Rr]e[Ss]harper 97 | *.DotSettings.user 98 | 99 | # JustCode is a .NET coding add-in 100 | .JustCode 101 | 102 | # TeamCity is a build add-in 103 | _TeamCity* 104 | 105 | # DotCover is a Code Coverage Tool 106 | *.dotCover 107 | 108 | # NCrunch 109 | _NCrunch_* 110 | .*crunch*.local.xml 111 | 112 | # MightyMoose 113 | *.mm.* 114 | AutoTest.Net/ 115 | 116 | # Web workbench (sass) 117 | .sass-cache/ 118 | 119 | # Installshield output folder 120 | [Ee]xpress/ 121 | 122 | # DocProject is a documentation generator add-in 123 | DocProject/buildhelp/ 124 | DocProject/Help/*.HxT 125 | DocProject/Help/*.HxC 126 | DocProject/Help/*.hhc 127 | DocProject/Help/*.hhk 128 | DocProject/Help/*.hhp 129 | DocProject/Help/Html2 130 | DocProject/Help/html 131 | 132 | # Click-Once directory 133 | publish/ 134 | 135 | # Publish Web Output 136 | *.[Pp]ublish.xml 137 | *.azurePubxml 138 | ## TODO: Comment the next line if you want to checkin your 139 | ## web deploy settings but do note that will include unencrypted 140 | ## passwords 141 | #*.pubxml 142 | 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | 154 | # Windows Azure Build Output 155 | csx/ 156 | *.build.csdef 157 | 158 | # Windows Store app package directory 159 | AppPackages/ 160 | 161 | # Visual Studio cache files 162 | # files ending in .cache can be ignored 163 | *.[Cc]ache 164 | # but keep track of directories ending in .cache 165 | !*.[Cc]ache/ 166 | 167 | # Others 168 | ClientBin/ 169 | [Ss]tyle[Cc]op.* 170 | ~$* 171 | *~ 172 | *.dbmdl 173 | *.dbproj.schemaview 174 | *.pfx 175 | *.publishsettings 176 | node_modules/ 177 | orleans.codegen.cs 178 | 179 | # RIA/Silverlight projects 180 | Generated_Code/ 181 | 182 | # Backup & report files from converting an old project file 183 | # to a newer Visual Studio version. Backup files are not needed, 184 | # because we have git ;-) 185 | _UpgradeReport_Files/ 186 | Backup*/ 187 | UpgradeLog*.XML 188 | UpgradeLog*.htm 189 | 190 | # SQL Server files 191 | *.mdf 192 | *.ldf 193 | 194 | # Business Intelligence projects 195 | *.rdl.data 196 | *.bim.layout 197 | *.bim_*.settings 198 | 199 | # Microsoft Fakes 200 | FakesAssemblies/ 201 | 202 | # Node.js Tools for Visual Studio 203 | .ntvs_analysis.dat 204 | 205 | # Visual Studio 6 build log 206 | *.plg 207 | 208 | # Visual Studio 6 workspace options file 209 | *.opt 210 | 211 | # LightSwitch generated files 212 | GeneratedArtifacts/ 213 | _Pvt_Extensions/ 214 | ModelManifest.xml 215 | -------------------------------------------------------------------------------- /src/Common.cs: -------------------------------------------------------------------------------- 1 | /* Filename: Common.cs 2 | * 3 | * Author: Rob Holme (rob@holme.com.au) 4 | * 5 | * Date: 29/08/2016 6 | * 7 | * Notes: Implements static functions common to more than one CmdLet class 8 | * 9 | */ 10 | 11 | namespace HL7Tools 12 | { 13 | using System.Management.Automation; 14 | using System.Text.RegularExpressions; 15 | using Microsoft.PowerShell.Commands; 16 | 17 | public static class Common 18 | { 19 | /// 20 | /// Confirm that the HL7 item location string is in a valid format. It does not check to see if the item referenced exists or not. 21 | /// 22 | /// 23 | /// 24 | public static bool IsItemLocationValid(string hl7ItemLocation) 25 | { 26 | // make sure the location requested mactches the regex of a valid location string. This does not check to see if segment names exit, or items are present in the message 27 | if (Regex.IsMatch(hl7ItemLocation, "^[A-Z]{2}([A-Z]|[0-9])([[]([1-9]|[1-9][0-9])[]])?(([-][0-9]{1,3}([[]([1-9]|[1-9][0-9])[]])?[.][0-9]{1,3}[.][0-9]{1,3})|([-][0-9]{1,3}([[]([1-9]|[1-9][0-9])[]])?[.][0-9]{1,3})|([-][0-9]{1,3}([[]([1-9]|[1-9][0-9])[]])?))?$", RegexOptions.IgnoreCase)) // regex to confirm the HL7 element location string is valid 28 | { 29 | // make sure field, component and subcomponent values are not 0 30 | if (Regex.IsMatch(hl7ItemLocation, "([.]0)|([-]0)", RegexOptions.IgnoreCase)) { 31 | return false; 32 | } 33 | return true; 34 | } 35 | return false; 36 | } 37 | 38 | /// 39 | /// Make sure the filter string matches the expected pattern for a filter 40 | /// 41 | /// 42 | /// 43 | public static bool IsFilterValid(string filterString) 44 | { 45 | if (Regex.IsMatch(filterString, "^[A-Z]{2}([A-Z]|[0-9])([[]([1-9]|[1-9][0-9])[]])?(([-][0-9]{1,3}([[]([1-9]|[1-9][0-9])[]])?[.][0-9]{1,3}[.][0-9]{1,3})|([-][0-9]{1,3}([[]([1-9]|[1-9][0-9])[]])?[.][0-9]{1,3})|([-][0-9]{1,3}([[]([1-9]|[1-9][0-9])[]])?))?=", RegexOptions.IgnoreCase)) { 46 | return true; 47 | } 48 | 49 | // the value provided after the -filter switch did not match the expected format of a message trigger. 50 | else { 51 | return false; 52 | } 53 | } 54 | 55 | /// 56 | /// return true if the string representing the HL7 location is valid. This does not confirm if the items exists, it only checks the formating of the string. 57 | /// 58 | /// The string identifying the location of the item within the message. eg PID-3.1 59 | /// 60 | public static bool IsHL7LocationStringValid(string HL7LocationString) 61 | { 62 | return (Regex.IsMatch(HL7LocationString, "^[A-Z]{2}([A-Z]|[0-9])([[]([1-9]|[1-9][0-9])[]])?(([-][0-9]{1,3}([[]([1-9]|[1-9][0-9])[]])?[.][0-9]{1,3}[.][0-9]{1,3})|([-][0-9]{1,3}([[]([1-9]|[1-9][0-9])[]])?[.][0-9]{1,3})|([-][0-9]{1,3}([[]([1-9]|[1-9][0-9])[]])?))?$", RegexOptions.IgnoreCase)); // segment([repeat])? or segment([repeat)?-field([repeat])? or segment([repeat)?-field([repeat])?.component or segment([repeat)?-field([repeat])?.component.subcomponent 63 | } 64 | 65 | /// 66 | /// Check that this provider is the filesystem 67 | /// 68 | /// 69 | /// 70 | /// 71 | public static bool IsFileSystemPath(ProviderInfo provider, string path) 72 | { 73 | bool isFileSystem = true; 74 | if (provider.ImplementingType != typeof(FileSystemProvider)) { 75 | // tell the caller that the item was not on the filesystem 76 | isFileSystem = false; 77 | } 78 | return isFileSystem; 79 | } 80 | 81 | /// 82 | /// return the portion of the filter string that identifies the HL7 Item to filter on 83 | /// 84 | /// 85 | /// 86 | public static string GetFilterItem(string filterString) 87 | { 88 | if (IsFilterValid(filterString)) { 89 | string[] tempString = (filterString).Split('='); 90 | return tempString[0]; 91 | } 92 | else { 93 | return null; 94 | } 95 | } 96 | 97 | /// 98 | /// return the portion of the filter string that identifies the value to filter on 99 | /// 100 | /// 101 | /// 102 | public static string GetFilterValue(string filterString) 103 | { 104 | if (IsFilterValid(filterString)) { 105 | string[] tempString = (filterString.Split('=')); 106 | if (tempString.Length > 1) { 107 | return tempString[1]; 108 | } 109 | else { 110 | return null; 111 | } 112 | } 113 | else { 114 | return null; 115 | } 116 | 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /help/Remove-HL7Identifiers.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: hl7tools.dll-Help.xml 3 | Module Name: hl7tools 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Remove-HL7Identifiers 9 | 10 | ## SYNOPSIS 11 | Remove personal identifiers for patients and next of kin from HL7 files. 12 | 13 | ## SYNTAX 14 | 15 | ### Literal 16 | ``` 17 | Remove-HL7Identifiers -LiteralPath [[-CustomItemsList] ] [[-MaskChar] ] 18 | [-OverwriteFile] [[-Encoding] ] [-ProgressAction ] [-WhatIf] [-Confirm] 19 | [] 20 | ``` 21 | 22 | ### Path 23 | ``` 24 | Remove-HL7Identifiers [-Path] [[-CustomItemsList] ] [[-MaskChar] ] [-OverwriteFile] 25 | [[-Encoding] ] [-ProgressAction ] [-WhatIf] [-Confirm] [] 26 | ``` 27 | 28 | ## DESCRIPTION 29 | Removes names, addresses and other personally identifiable details from a HL7 v2.x Message. 30 | 31 | ## EXAMPLES 32 | 33 | ### Example 1 34 | ```powershell 35 | PS C:\> Remove-HL7Identifiers -Path c:\hl7files\*.hl7 -OverwriteFile 36 | ``` 37 | 38 | ### Example 1 39 | ```powershell 40 | PS C:\> Remove-Hl7Identifiers -Path c:\test.txt 41 | ``` 42 | 43 | ### Example 1 44 | ```powershell 45 | PS C:\> Remove-HL7Identifiers -Path c:\test\testfile.hl7 -CustomItemsList PID-3.1,NK1,DG1 46 | ``` 47 | 48 | ## PARAMETERS 49 | 50 | ### -Confirm 51 | Prompts you for confirmation before running the cmdlet. 52 | 53 | ```yaml 54 | Type: SwitchParameter 55 | Parameter Sets: (All) 56 | Aliases: cf 57 | 58 | Required: False 59 | Position: Named 60 | Default value: None 61 | Accept pipeline input: False 62 | Accept wildcard characters: False 63 | ``` 64 | 65 | ### -CustomItemsList 66 | A list of the HL7 items to mask instead of the default segments and fields. 67 | List each item in a comma separated list (no spaces). 68 | Items can include a mix of segments (e.g. PV1), Fields (e.g. PID-3), Components (e.g. "PID-3.1") or Subcomponents (e.g. "PV1-42.2.2"). 69 | For repeating segments and fields a specific repeat can be identified (index starts at 1). 70 | e.g. "PID-3\[1\].1" will mask out the first occurrence of "PID-3.1", leaving other repeats of "PID-3.1" unchanged. 71 | Likewise "IN1\[2\]" will mask out the second occurrence of the IN1 segments only. 72 | e.g. -CustomItemsList PID-3.1,PID-5,PID-13,NK1 73 | 74 | ```yaml 75 | Type: String[] 76 | Parameter Sets: (All) 77 | Aliases: 78 | 79 | Required: False 80 | Position: 1 81 | Default value: None 82 | Accept pipeline input: False 83 | Accept wildcard characters: False 84 | ``` 85 | 86 | ### -Encoding 87 | Text encoding ('UTF-8' | 'ISO-8859-1'). Defaults to "UTF-8". 88 | 89 | ```yaml 90 | Type: String 91 | Parameter Sets: (All) 92 | Aliases: 93 | Accepted values: UTF-8, ISO-8859-1 94 | 95 | Required: False 96 | Position: 4 97 | Default value: None 98 | Accept pipeline input: False 99 | Accept wildcard characters: False 100 | ``` 101 | 102 | ### -LiteralPath 103 | Same as -Path, only wildcards are not expanded. 104 | Use this if the literal path includes a wildcard character you do not intent to expand. 105 | 106 | ```yaml 107 | Type: String[] 108 | Parameter Sets: Literal 109 | Aliases: PSPath, Name, Filename 110 | 111 | Required: True 112 | Position: Named 113 | Default value: None 114 | Accept pipeline input: True (ByPropertyName) 115 | Accept wildcard characters: False 116 | ``` 117 | 118 | ### -MaskChar 119 | The character used to mask the identifier fields, Defaults to '*' if not supplied. 120 | 121 | ```yaml 122 | Type: Char 123 | Parameter Sets: (All) 124 | Aliases: 125 | 126 | Required: False 127 | Position: 2 128 | Default value: None 129 | Accept pipeline input: False 130 | Accept wildcard characters: False 131 | ``` 132 | 133 | ### -OverwriteFile 134 | If this switch is set, the original file is modified. 135 | 136 | ```yaml 137 | Type: SwitchParameter 138 | Parameter Sets: (All) 139 | Aliases: 140 | 141 | Required: False 142 | Position: 3 143 | Default value: None 144 | Accept pipeline input: False 145 | Accept wildcard characters: False 146 | ``` 147 | 148 | ### -Path 149 | The full or relative path a single HL7 file or directory, or a list of files. 150 | This may include wildcards in the path name. 151 | If a directory is provided, all files within the directory will be examined. 152 | Exceptions will be raised if a file isn't identified as a HL7 v2.x file. 153 | This parameter accepts a list of files, separate each file file with a ','. 154 | 155 | ```yaml 156 | Type: String[] 157 | Parameter Sets: Path 158 | Aliases: 159 | 160 | Required: True 161 | Position: 0 162 | Default value: None 163 | Accept pipeline input: True (ByPropertyName, ByValue) 164 | Accept wildcard characters: False 165 | ``` 166 | 167 | ### -WhatIf 168 | Shows what would happen if the cmdlet runs. 169 | The cmdlet is not run. 170 | 171 | ```yaml 172 | Type: SwitchParameter 173 | Parameter Sets: (All) 174 | Aliases: wi 175 | 176 | Required: False 177 | Position: Named 178 | Default value: None 179 | Accept pipeline input: False 180 | Accept wildcard characters: False 181 | ``` 182 | 183 | ### -ProgressAction 184 | {{ Fill ProgressAction Description }} 185 | 186 | ```yaml 187 | Type: ActionPreference 188 | Parameter Sets: (All) 189 | Aliases: proga 190 | 191 | Required: False 192 | Position: Named 193 | Default value: None 194 | Accept pipeline input: False 195 | Accept wildcard characters: False 196 | ``` 197 | 198 | ### CommonParameters 199 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 200 | 201 | ## INPUTS 202 | 203 | ### System.String[] 204 | 205 | ## OUTPUTS 206 | 207 | ### System.Object 208 | ## NOTES 209 | 210 | ## RELATED LINKS 211 | 212 | [Online Help](https://github.com/RobHolme/HL7-Powershell-Module#remove-hl7identifiers) -------------------------------------------------------------------------------- /help/Select-HL7Item.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: hl7tools.dll-Help.xml 3 | Module Name: hl7tools 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Select-HL7Item 9 | 10 | ## SYNOPSIS 11 | Queries a HL7 file (or list of files) to return a specific HL7 Item. 12 | 13 | ## SYNTAX 14 | 15 | ### Literal 16 | ``` 17 | Select-HL7Item -LiteralPath [-ItemPosition] [[-Filter] ] [[-Encoding] ] 18 | [-ProgressAction ] [] 19 | ``` 20 | 21 | ### Path 22 | ``` 23 | Select-HL7Item [-Path] [-ItemPosition] [[-Filter] ] [[-Encoding] ] 24 | [-ProgressAction ] [] 25 | ``` 26 | 27 | ## DESCRIPTION 28 | This CmdLet returns the values of specific HL7 items from a file (or group of files). 29 | This is intended to aid with integration work to validate the values of fields over a large sample of HL7 messages. 30 | Some basic filtering is available to include specific messages from a larger sample. 31 | 32 | Output can be piped to other PowerShell CmdLets to refine the results. 33 | e.g. returning only the unique values across a range of files: 34 | 35 | ## EXAMPLES 36 | 37 | ### Example 1 38 | ```powershell 39 | PS C:\> Select-HL7Item -Path c:\test -ItemPosition PID-3.1 40 | ``` 41 | 42 | Display the Patient ID values for all hl7 files in c:\test 43 | 44 | ### Example 2 45 | ```powershell 46 | PS C:\> get-childitem *.hl7 | Select-HL7Item -ItemPosition PID-3.1,PID-5 47 | ``` 48 | 49 | Pipe all files with .hl7 extensions to the CmdLet, return Patient ID values (PID-3.1) and Patient Names (PID-5). 50 | 51 | ### Example 3 52 | ```powershell 53 | PS C:\> Select-HL7Item -Path c:\test -ItemPosition PID-5 -Filter PV1-2=INPATIENT 54 | ``` 55 | 56 | Display all PID-5 values where the value of PV1-2 is INPATIENT. 57 | 58 | ### Example 4 59 | ```powershell 60 | PS C:\> (Select-HL7Item -Path c:\test -ItemPosition PID-3.1).ItemValue | Sort-Object -Unique 61 | ``` 62 | 63 | Only return unique values 64 | 65 | ## PARAMETERS 66 | 67 | ### -Encoding 68 | Text encoding ('UTF-8' | 'ISO-8859-1'). Defaults to "UTF-8". 69 | 70 | ```yaml 71 | Type: String 72 | Parameter Sets: (All) 73 | Aliases: 74 | Accepted values: UTF-8, ISO-8859-1 75 | 76 | Required: False 77 | Position: 3 78 | Default value: None 79 | Accept pipeline input: False 80 | Accept wildcard characters: False 81 | ``` 82 | 83 | ### -Filter 84 | Only includes messages where a HL7 item equals a specific value. 85 | The format is: HL7Item=value. 86 | The HL7Item part of the filter is of the same format as the -ItemPosition parameter. 87 | The -Filter parameter accepts a list of filters (separated by a comma). 88 | If a list of filters is provided then a message must match all conditions to be included. 89 | e.g. 90 | 91 | -Filter MSH-9=ADT^A05 This filter would only include messages that had "ADT^A05" as the value for the MSH-9 field. 92 | 93 | -Filter MSH-9=ADT^A05,PV1-2=OUTPATIENT This filter would only include messages where both the MSH-9 field contained "ADT^A04" and the PV1-2 field contained "OUTPATIENT" 94 | 95 | ```yaml 96 | Type: String[] 97 | Parameter Sets: (All) 98 | Aliases: 99 | 100 | Required: False 101 | Position: 2 102 | Default value: None 103 | Accept pipeline input: False 104 | Accept wildcard characters: False 105 | ``` 106 | 107 | ### -ItemPosition 108 | A string, or list of strings, identifying the location of the item(s) in the HL7 message you wish to retrieve the value for. 109 | e.g. 110 | 111 | PID Identifies the PID Segment 112 | PID-3 Identifies the PID-3 Field 113 | PID-3.1 Identifies the PID-3.1 Component 114 | PID-3.1.1 Identifies the PID-3.1.1 Subcomponent 115 | PID-3\[2\].1 Identifies the PID-3.1 Component for second occurrence of the PID-3 Field 116 | 117 | ```yaml 118 | Type: String[] 119 | Parameter Sets: (All) 120 | Aliases: Item 121 | 122 | Required: True 123 | Position: 1 124 | Default value: None 125 | Accept pipeline input: False 126 | Accept wildcard characters: False 127 | ``` 128 | 129 | ### -LiteralPath 130 | Same as -Path, only wildcards are not expanded. 131 | Use this if the literal path includes a wildcard character you do not intent to expand. 132 | 133 | ```yaml 134 | Type: String[] 135 | Parameter Sets: Literal 136 | Aliases: PSPath, Name, Filename 137 | 138 | Required: True 139 | Position: Named 140 | Default value: None 141 | Accept pipeline input: True (ByPropertyName) 142 | Accept wildcard characters: False 143 | ``` 144 | 145 | ### -Path 146 | The full or relative path a single HL7 file or directory. 147 | This may include wildcards in the path name. 148 | If a directory is provided, all files within the directory will be examined. 149 | Exceptions will be raised if a file isn't identified as a HL7 v2.x file. 150 | This parameter accepts a list of files, separate each file file with a ','. 151 | 152 | ```yaml 153 | Type: String[] 154 | Parameter Sets: Path 155 | Aliases: 156 | 157 | Required: True 158 | Position: 0 159 | Default value: None 160 | Accept pipeline input: True (ByPropertyName, ByValue) 161 | Accept wildcard characters: False 162 | ``` 163 | 164 | ### -ProgressAction 165 | {{ Fill ProgressAction Description }} 166 | 167 | ```yaml 168 | Type: ActionPreference 169 | Parameter Sets: (All) 170 | Aliases: proga 171 | 172 | Required: False 173 | Position: Named 174 | Default value: None 175 | Accept pipeline input: False 176 | Accept wildcard characters: False 177 | ``` 178 | 179 | ### CommonParameters 180 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 181 | 182 | ## INPUTS 183 | 184 | ### System.String[] 185 | 186 | ## OUTPUTS 187 | 188 | ### System.Object 189 | ## NOTES 190 | 191 | ## RELATED LINKS 192 | 193 | [Online help](https://github.com/RobHolme/HL7-Powershell-Module#select-hl7item) -------------------------------------------------------------------------------- /help/Remove-HL7Item.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: hl7tools.dll-Help.xml 3 | Module Name: hl7tools 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Remove-HL7Item 9 | 10 | ## SYNOPSIS 11 | Delete the value for a nominated item in the HL7 message. 12 | 13 | ## SYNTAX 14 | 15 | ### Literal 16 | ``` 17 | Remove-HL7Item -LiteralPath [-ItemPosition] [[-Filter] ] [-RemoveAllRepeats] 18 | [[-Encoding] ] [-ProgressAction ] [-WhatIf] [-Confirm] [] 19 | ``` 20 | 21 | ### Path 22 | ``` 23 | Remove-HL7Item [-Path] [-ItemPosition] [[-Filter] ] [-RemoveAllRepeats] 24 | [[-Encoding] ] [-ProgressAction ] [-WhatIf] [-Confirm] [] 25 | ``` 26 | 27 | ## DESCRIPTION 28 | Deletes the value of specified items from the HL7 message. 29 | A list of items, or single item can be specified. 30 | 31 | ## EXAMPLES 32 | 33 | ### Example 1 34 | ```powershell 35 | PS C:\> Remove-HL7Item -Path c:\hl7\test.hl7 -ItemPosition PID-3.1 36 | ``` 37 | 38 | remove the PID-3.1 component 39 | 40 | ### Example 2 41 | 42 | ```powershell 43 | PS C:\> Remove-HL7Item -Path c:\hl7\test.hl7 -ItemPosition PID-3.1 -Filter "MSH-9.2=ADT^A01" 44 | ``` 45 | 46 | remove the PID-3.1 component for all ADT^A01 messages 47 | 48 | ## PARAMETERS 49 | 50 | ### -Confirm 51 | Prompts you for confirmation before running the cmdlet. 52 | 53 | ```yaml 54 | Type: SwitchParameter 55 | Parameter Sets: (All) 56 | Aliases: cf 57 | 58 | Required: False 59 | Position: Named 60 | Default value: None 61 | Accept pipeline input: False 62 | Accept wildcard characters: False 63 | ``` 64 | 65 | ### -Encoding 66 | Text encoding ('UTF-8' | 'ISO-8859-1'). Defaults to "UTF-8". 67 | 68 | ```yaml 69 | Type: String 70 | Parameter Sets: (All) 71 | Aliases: 72 | Accepted values: UTF-8, ISO-8859-1 73 | 74 | Required: False 75 | Position: 3 76 | Default value: None 77 | Accept pipeline input: False 78 | Accept wildcard characters: False 79 | ``` 80 | 81 | ### -Filter 82 | Only includes messages where a HL7 item equals a specific value. 83 | The format is: HL7Item=value. 84 | The HL7Item part of the filter is of the same format as the -ItemPosition parameter. 85 | The -Filter parameter accepts a list of filters (separated by a comma). 86 | If a list of filters is provided then a message must match all conditions to be included. 87 | e.g. 88 | 89 | -Filter MSH-9=ADT^A05 This filter would only include messages that had "ADT^A05" as the value for the MSH-9 field. 90 | 91 | -Filter MSH-9=ADT^A05,PV1-2=OUTPATIENT This filter would only include messages where both the MSH-9 field contained "ADT^A04" and the PV1-2 field contained "OUTPATIENT" 92 | 93 | 94 | ```yaml 95 | Type: String[] 96 | Parameter Sets: (All) 97 | Aliases: 98 | 99 | Required: False 100 | Position: 2 101 | Default value: None 102 | Accept pipeline input: False 103 | Accept wildcard characters: False 104 | ``` 105 | 106 | ### -ItemPosition 107 | A string identifying the location of the item in the HL7 message you wish to retrieve the value for. 108 | e.g. 109 | 110 | PID Identifies the PID Segment 111 | PID-3 Identifies the PID-3 Field 112 | PID-3.1 Identifies the PID-3.1 Component 113 | PID-3.1.1 Identifies the PID-3.1.1 Subcomponent 114 | PID-3\[2\].1 Identifies the PID-3.1 Component for second occurrence of the PID-3 Field 115 | 116 | ```yaml 117 | Type: String[] 118 | Parameter Sets: (All) 119 | Aliases: Item 120 | 121 | Required: True 122 | Position: 1 123 | Default value: None 124 | Accept pipeline input: False 125 | Accept wildcard characters: False 126 | ``` 127 | 128 | ### -LiteralPath 129 | Same as -Path, only wildcards are not expanded. 130 | Use this if the literal path includes a wildcard character you do not intent to expand. 131 | 132 | ```yaml 133 | Type: String[] 134 | Parameter Sets: Literal 135 | Aliases: PSPath, Name, Filename 136 | 137 | Required: True 138 | Position: Named 139 | Default value: None 140 | Accept pipeline input: True (ByPropertyName) 141 | Accept wildcard characters: False 142 | ``` 143 | 144 | ### -Path 145 | The full or relative path a single HL7 file or directory. 146 | This may include wildcards in the path name. 147 | If a directory is provide, all files within the directory will be examined. 148 | Exceptions will be raised if a file isn't identified as a HL7 v2.x file. 149 | This parameter accepts a list of files, separate each file file with a ','. 150 | 151 | ```yaml 152 | Type: String[] 153 | Parameter Sets: Path 154 | Aliases: 155 | 156 | Required: True 157 | Position: 0 158 | Default value: None 159 | Accept pipeline input: True (ByPropertyName, ByValue) 160 | Accept wildcard characters: False 161 | ``` 162 | 163 | ### -RemoveAllRepeats 164 | Delete all repeats identified, Defaults to only deleting the first occurrence if this switch is not set. 165 | 166 | ```yaml 167 | Type: SwitchParameter 168 | Parameter Sets: (All) 169 | Aliases: 170 | 171 | Required: False 172 | Position: Named 173 | Default value: None 174 | Accept pipeline input: False 175 | Accept wildcard characters: False 176 | ``` 177 | 178 | ### -WhatIf 179 | Shows what would happen if the cmdlet runs. 180 | The cmdlet is not run. 181 | 182 | ```yaml 183 | Type: SwitchParameter 184 | Parameter Sets: (All) 185 | Aliases: wi 186 | 187 | Required: False 188 | Position: Named 189 | Default value: None 190 | Accept pipeline input: False 191 | Accept wildcard characters: False 192 | ``` 193 | 194 | ### -ProgressAction 195 | {{ Fill ProgressAction Description }} 196 | 197 | ```yaml 198 | Type: ActionPreference 199 | Parameter Sets: (All) 200 | Aliases: proga 201 | 202 | Required: False 203 | Position: Named 204 | Default value: None 205 | Accept pipeline input: False 206 | Accept wildcard characters: False 207 | ``` 208 | 209 | ### CommonParameters 210 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 211 | 212 | ## INPUTS 213 | 214 | ### System.String[] 215 | 216 | ## OUTPUTS 217 | 218 | ### System.Object 219 | ## NOTES 220 | 221 | ## RELATED LINKS 222 | 223 | [Online Help](https://github.com/RobHolme/HL7-Powershell-Module#remove-hl7item) -------------------------------------------------------------------------------- /src/ReceiveHL7Message.cs: -------------------------------------------------------------------------------- 1 | /* Filename: ReceiveHL7Message.cs 2 | * 3 | * Author: Rob Holme (rob@holme.com.au) 4 | * 5 | * Date: 25/02/2017 6 | * 7 | * Notes: Implements a cmdlet to receive a MLLP framed HL7 message via TCP. Messages reveived are written to disk. 8 | * 9 | */ 10 | 11 | namespace HL7Tools 12 | { 13 | using System; 14 | using System.IO; 15 | using System.Management.Automation; 16 | using System.Threading; 17 | using System.Collections.Concurrent; 18 | 19 | [Cmdlet("Receive", "HL7Message")] 20 | public class ReceiveHL7Message : PSCmdlet 21 | { 22 | private int port; 23 | private bool noACK; 24 | private string path; 25 | private bool abortProcessing = false; 26 | private int timeout = 60000; // default to 1 minute 27 | private string encoding = "UTF-8"; 28 | private ConcurrentQueue objectQueue = new ConcurrentQueue(); 29 | private ConcurrentQueue warningQueue = new ConcurrentQueue(); 30 | private ConcurrentQueue verboseQueue = new ConcurrentQueue(); 31 | 32 | [Parameter( 33 | Position = 0, 34 | HelpMessage = "The path to save received messages to", 35 | Mandatory = true) 36 | 37 | ] 38 | [ValidateNotNullOrEmpty] 39 | public string Path 40 | { 41 | get { return this.path; } 42 | set 43 | { 44 | this.path = value; 45 | } 46 | } 47 | 48 | // The port number to listen on 49 | [Parameter( 50 | Mandatory = true, 51 | Position = 1, 52 | HelpMessage = "The TCP port to listen on" 53 | )] 54 | [ValidateRange(1, 65535)] 55 | public int Port 56 | { 57 | get { return this.port; } 58 | set { this.port = value; } 59 | } 60 | 61 | // The timeout to terminate idle TCP connections in seconds (stored in milliseconds) 62 | [Parameter( 63 | Mandatory = false, 64 | Position = 2, 65 | HelpMessage = "The timeout to end idle TCP connections in seconds." 66 | )] 67 | [ValidateRange(1, 1800)] 68 | public int Timeout 69 | { 70 | get { return (this.timeout / 1000); } 71 | set { this.timeout = (value*1000); } 72 | } 73 | 74 | // The encoding used when receiving the message 75 | [Parameter( 76 | Mandatory = false, 77 | Position = 3, 78 | HelpMessage = "Message text encoding" 79 | )] 80 | [ValidateSet("UTF-8", "ISO-8859-1")] 81 | public string Encoding 82 | { 83 | get { return this.encoding; } 84 | set { this.encoding = value; } 85 | } 86 | 87 | // Do not wait for ACKs responses if this switch is set 88 | [Parameter( 89 | Mandatory = false, 90 | HelpMessage = "Do not wait for ACK response" 91 | )] 92 | public SwitchParameter NoACK 93 | { 94 | get { return this.noACK; } 95 | set { this.noACK = value; } 96 | } 97 | 98 | /// 99 | /// Validate items before processing the pipeline. Only called once before all items in the pipeline processed by BeginProcessing() 100 | /// 101 | protected override void BeginProcessing() 102 | { 103 | if (!Directory.Exists(this.path)) { 104 | WriteWarning("The path " + this.path + " does not exist, or could not be accessed."); 105 | this.abortProcessing = true; 106 | } 107 | else { 108 | // expand the path 109 | this.path = System.IO.Path.GetFullPath(this.path); 110 | 111 | } 112 | } 113 | 114 | /// 115 | /// Listen for incoming MLLP framed messages 116 | /// 117 | protected override void ProcessRecord() 118 | { 119 | // if any of the validation checks in BeginProcessing() has failed, return without processing. 120 | if (abortProcessing) { 121 | return; 122 | } 123 | WriteWarning("Listening for MLLP framed messages on port " + this.port + ". Press 'ESC' key to exit."); 124 | 125 | // create a new instance of HL7TCPListener. Set optional properties to return ACKs, passthru messages, archive location. Start the listener. 126 | HL7TCPListener listener = new HL7TCPListener(port, ref objectQueue, ref warningQueue, ref verboseQueue, this.timeout, this.encoding); 127 | if (noACK) { 128 | listener.SendACK = false; 129 | } 130 | else { 131 | listener.SendACK = true; 132 | } 133 | if (path != null) { 134 | listener.FilePath = path; 135 | WriteVerbose("Saving received files to " + listener.FilePath); 136 | } 137 | if (!listener.Start()) { 138 | WriteWarning("Failed to start TCP Listener"); 139 | } 140 | else { 141 | while (listener.IsRunning()) { 142 | // dequeue and write any warning messages received from TCPHL7Listener 143 | string tempMessage; 144 | while (warningQueue.TryDequeue(out tempMessage)) { 145 | WriteWarning(tempMessage); 146 | } 147 | // dequeue and write any result objects 148 | ReceivedMessageResult tempResult; 149 | while (objectQueue.TryDequeue(out tempResult)) { 150 | WriteObject(tempResult); 151 | } 152 | // dequeue and write and verbose logging to the console 153 | string tempVerbose; 154 | while (verboseQueue.TryDequeue(out tempVerbose)) { 155 | WriteVerbose(tempVerbose); 156 | } 157 | 158 | // check to see if the user has exited 159 | if (Console.KeyAvailable) { 160 | ConsoleKeyInfo keyInfo = Console.ReadKey(true); 161 | if (keyInfo.Key == ConsoleKey.Escape) { 162 | WriteWarning("Exiting."); 163 | listener.RequestStop(); 164 | return; 165 | } 166 | } 167 | // sleep before checking the queues again 168 | Thread.Sleep(1000); 169 | } 170 | } 171 | } 172 | 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /help/Send-HL7Message.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: hl7tools.dll-Help.xml 3 | Module Name: hl7tools 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Send-HL7Message 9 | 10 | ## SYNOPSIS 11 | Send a HL7 file to a remote host via TCP (MLLP). 12 | 13 | ## SYNTAX 14 | 15 | ### Literal 16 | ``` 17 | Send-HL7Message [-LiteralPath] [-HostName] [-Port] [-NoACK] [[-Delay] ] 18 | [[-Encoding] ] [-UseTLS] [-SkipCertificateCheck] [-ProgressAction ] 19 | [] 20 | ``` 21 | 22 | ### Path 23 | ``` 24 | Send-HL7Message [-Path] [-HostName] [-Port] [-NoACK] [[-Delay] ] 25 | [[-Encoding] ] [-UseTLS] [-SkipCertificateCheck] [-ProgressAction ] 26 | [] 27 | ``` 28 | 29 | ### MessageString 30 | ``` 31 | Send-HL7Message [-MessageString] [-HostName] [-Port] [-NoACK] [[-Delay] ] 32 | [[-Encoding] ] [-UseTLS] [-SkipCertificateCheck] [-ProgressAction ] 33 | [] 34 | ``` 35 | 36 | ## DESCRIPTION 37 | Send a HL7 v2.x message from a file (or list of files) via TCP to a remote endpoint. 38 | Messages are framed using MLLP (Minimal Lower Layer Protocol). 39 | 40 | ## EXAMPLES 41 | 42 | ### Example 1 43 | ```powershell 44 | PS C:\> Send-Hl7Message -Hostname 192.168.0.10 -Port 1234 -Path c:\HL7Files\message1.hl7 45 | ``` 46 | 47 | ### Example 2 48 | ```powershell 49 | PS C:\> 50 | Send-Hl7Message -Hostname 192.168.0.10 -Port 1234 -Path c:\HL7Files\*.hl7 -Encoding ISO-8859-1 51 | ``` 52 | 53 | Send all .hl7 files in the c:\HL7Files folder. Use ISO-8859-1 (Wester European) text encoding. 54 | 55 | ### Example 3 56 | ```powershell 57 | PS C:\> $msg = get-content c:\hl7\test.hl7; Send-Hl7Message -Hostname 192.168.0.10 -Port 1234 -MessageString $msg 58 | ``` 59 | 60 | Supply the message contents as a parameter. Accepts a string array [string[]] (one one segment per array item) or a single string value [string] (with Carriage Returns between each segment.) 61 | 62 | ## PARAMETERS 63 | 64 | ### -Delay 65 | The delay (in seconds) between sending each message. 66 | 67 | ```yaml 68 | Type: Int32 69 | Parameter Sets: (All) 70 | Aliases: 71 | 72 | Required: False 73 | Position: 3 74 | Default value: None 75 | Accept pipeline input: False 76 | Accept wildcard characters: False 77 | ``` 78 | 79 | ### -Encoding 80 | Set the text encoding. 81 | Supports "UTF-8" or "ISO-8859-1" (Western European). 82 | Defaults to "UTF-8" if parameter not supplied. 83 | 84 | 85 | ```yaml 86 | Type: String 87 | Parameter Sets: (All) 88 | Aliases: 89 | Accepted values: UTF-8, ISO-8859-1 90 | 91 | Required: False 92 | Position: 4 93 | Default value: None 94 | Accept pipeline input: False 95 | Accept wildcard characters: False 96 | ``` 97 | 98 | ### -HostName 99 | The IP Address or host name of the remote host to send the HL7 message to. 100 | 101 | ```yaml 102 | Type: String 103 | Parameter Sets: (All) 104 | Aliases: ComputerName, Server, IPAddress 105 | 106 | Required: True 107 | Position: 1 108 | Default value: None 109 | Accept pipeline input: False 110 | Accept wildcard characters: False 111 | ``` 112 | 113 | ### -LiteralPath 114 | Same as -Path, only wildcards are not expanded. 115 | Use this if the literal path includes a wildcard character you do not intent to expand. 116 | 117 | ```yaml 118 | Type: String[] 119 | Parameter Sets: Literal 120 | Aliases: PSPath, Name, Filename 121 | 122 | Required: True 123 | Position: 0 124 | Default value: None 125 | Accept pipeline input: True (ByPropertyName) 126 | Accept wildcard characters: False 127 | ``` 128 | 129 | ### -MessageString 130 | Supply the message contents as a string value instead of a filename. Accepts either a string[] array [string[]] (one one segment per array item) or a single [string] value (with Carriage Returns between each segment in a single string.) 131 | 132 | ```yaml 133 | Type: String[] 134 | Parameter Sets: MessageString 135 | Aliases: 136 | 137 | Required: True 138 | Position: 0 139 | Default value: None 140 | Accept pipeline input: False 141 | Accept wildcard characters: False 142 | ``` 143 | 144 | ### -NoACK 145 | This switch instructs the CmdLet not to wait for an ACK response from the remote host. 146 | 147 | ```yaml 148 | Type: SwitchParameter 149 | Parameter Sets: (All) 150 | Aliases: 151 | 152 | Required: False 153 | Position: Named 154 | Default value: None 155 | Accept pipeline input: False 156 | Accept wildcard characters: False 157 | ``` 158 | 159 | ### -Path 160 | The full or relative path a single HL7 file or directory. 161 | This may include wildcards in the path name. 162 | If a directory is provide, all files within the directory will be examined. 163 | Exceptions will be raised if a file isn't identified as a HL7 v2.x file. 164 | This parameter accepts a list of files, separate each file file with a ',' (no spaces). 165 | 166 | ```yaml 167 | Type: String[] 168 | Parameter Sets: Path 169 | Aliases: 170 | 171 | Required: True 172 | Position: 0 173 | Default value: None 174 | Accept pipeline input: False 175 | Accept wildcard characters: False 176 | ``` 177 | 178 | ### -Port 179 | The TCP port of the listener on the remote host to send the HL7 message to. 180 | 181 | ```yaml 182 | Type: Int32 183 | Parameter Sets: (All) 184 | Aliases: 185 | 186 | Required: True 187 | Position: 2 188 | Default value: None 189 | Accept pipeline input: False 190 | Accept wildcard characters: False 191 | ``` 192 | 193 | ### -SkipCertificateCheck 194 | Ignore TLS certificate errors. 195 | 196 | ```yaml 197 | Type: SwitchParameter 198 | Parameter Sets: (All) 199 | Aliases: 200 | 201 | Required: False 202 | Position: Named 203 | Default value: None 204 | Accept pipeline input: False 205 | Accept wildcard characters: False 206 | ``` 207 | 208 | ### -UseTLS 209 | Use TLS to secure the connection (if supported by server). 210 | 211 | ```yaml 212 | Type: SwitchParameter 213 | Parameter Sets: (All) 214 | Aliases: 215 | 216 | Required: False 217 | Position: Named 218 | Default value: None 219 | Accept pipeline input: False 220 | Accept wildcard characters: False 221 | ``` 222 | 223 | ### -ProgressAction 224 | {{ Fill ProgressAction Description }} 225 | 226 | ```yaml 227 | Type: ActionPreference 228 | Parameter Sets: (All) 229 | Aliases: proga 230 | 231 | Required: False 232 | Position: Named 233 | Default value: None 234 | Accept pipeline input: False 235 | Accept wildcard characters: False 236 | ``` 237 | 238 | ### CommonParameters 239 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 240 | 241 | ## INPUTS 242 | 243 | ### System.String[] 244 | 245 | ## OUTPUTS 246 | 247 | ### System.Object 248 | ## NOTES 249 | 250 | ## RELATED LINKS 251 | 252 | [Online Help](https://github.com/RobHolme/HL7-Powershell-Module#send-hl7message) -------------------------------------------------------------------------------- /help/Set-HL7Item.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: hl7tools.dll-Help.xml 3 | Module Name: hl7tools 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Set-HL7Item 9 | 10 | ## SYNOPSIS 11 | Sets the value of an existing item within a HL7 file. 12 | 13 | ## SYNTAX 14 | 15 | ### Literal 16 | ``` 17 | Set-HL7Item -LiteralPath [-ItemPosition] [-Value] [[-Filter] ] 18 | [[-Encoding] ] [-UpdateAllRepeats] [-AppendToExistingValue] [-ProgressAction ] 19 | [-WhatIf] [-Confirm] [] 20 | ``` 21 | 22 | ### Path 23 | ``` 24 | Set-HL7Item [-Path] [-ItemPosition] [-Value] [[-Filter] ] 25 | [[-Encoding] ] [-UpdateAllRepeats] [-AppendToExistingValue] [-ProgressAction ] 26 | [-WhatIf] [-Confirm] [] 27 | ``` 28 | 29 | ## DESCRIPTION 30 | This CmdLet changes the value of an existing HL7 item from a file (or group of files). 31 | Some basic filtering is available to only include specific messages within a large sample. 32 | By default only the first occurrence of an item will be changed unless the -UpdateAllRepeats switch is set. 33 | Items that cannot be located in the message will display a warning. 34 | To suppress the warning messages use the "-WarningAction Ignore" common parameter. 35 | 36 | ## EXAMPLES 37 | 38 | ### Example 1 39 | ```powershell 40 | PS C:\> Set-HL7Item -Path c:\hl7files\hl7file.txt -ItemPosition PID-3.1 -Value A1234567 41 | ``` 42 | 43 | ### Example 2 44 | ```powershell 45 | PS C:\> Set-HL7Item -Path c:\hl7files\*.hl7 -ItemPosition PV1-3.1 -Value A1234567 -Filter PV1-2=INPATIENT 46 | ``` 47 | 48 | ## PARAMETERS 49 | 50 | ### -AppendToExistingValue 51 | The value supplied by the '-Value' parameter is appended to the original value in the message (instead of replacing it). 52 | 53 | ```yaml 54 | Type: SwitchParameter 55 | Parameter Sets: (All) 56 | Aliases: Append 57 | 58 | Required: False 59 | Position: Named 60 | Default value: None 61 | Accept pipeline input: False 62 | Accept wildcard characters: False 63 | ``` 64 | 65 | ### -Confirm 66 | Prompts you for confirmation before running the cmdlet. 67 | 68 | ```yaml 69 | Type: SwitchParameter 70 | Parameter Sets: (All) 71 | Aliases: cf 72 | 73 | Required: False 74 | Position: Named 75 | Default value: None 76 | Accept pipeline input: False 77 | Accept wildcard characters: False 78 | ``` 79 | 80 | ### -Encoding 81 | Specify the character encoding. 82 | Supports "UTF-8" or "ISO-8859-1" (Western European). 83 | Defaults to "UTF-8" if parameter not supplied. 84 | 85 | ```yaml 86 | Type: String 87 | Parameter Sets: (All) 88 | Aliases: 89 | Accepted values: UTF-8, ISO-8859-1 90 | 91 | Required: False 92 | Position: 4 93 | Default value: None 94 | Accept pipeline input: False 95 | Accept wildcard characters: False 96 | ``` 97 | 98 | ### -Filter 99 | Only includes messages where a HL7 item equals a specific value. 100 | The format is: HL7Item=value. 101 | The HL7Item part of the filter is of the same format as the -ItemPosition parameter. 102 | The -Filter parameter accepts a list of filters (separated by a comma). 103 | If a list of filters is provided then a message must match all conditions to be included. 104 | e.g. 105 | 106 | -Filter MSH-9=ADT^A05 This filter would only include messages that had "ADT^A05" as the value for the MSH-9 field. 107 | 108 | -Filter MSH-9=ADT^A05,PV1-2=OUTPATIENT This filter would only include messages where both the MSH-9 field contained "ADT^A04" and the PV1-2 field contained "OUTPATIENT" 109 | 110 | ```yaml 111 | Type: String[] 112 | Parameter Sets: (All) 113 | Aliases: 114 | 115 | Required: False 116 | Position: 3 117 | Default value: None 118 | Accept pipeline input: False 119 | Accept wildcard characters: False 120 | ``` 121 | 122 | ### -ItemPosition 123 | A string identifying the location of the item in the HL7 message you wish to retrieve the value for. 124 | e.g. 125 | 126 | PID Identifies the PID Segment 127 | PID-3 Identifies the PID-3 Field 128 | PID-3.1 Identifies the PID-3.1 Component 129 | PID-3.1.1 Identifies the PID-3.1.1 Subcomponent 130 | PID-3\[2\].1 Identifies the PID-3.1 Component for second occurrence of the PID-3 Field 131 | 132 | ```yaml 133 | Type: String 134 | Parameter Sets: (All) 135 | Aliases: Item 136 | 137 | Required: True 138 | Position: 1 139 | Default value: None 140 | Accept pipeline input: False 141 | Accept wildcard characters: False 142 | ``` 143 | 144 | ### -LiteralPath 145 | Same as -Path, only wildcards are not expanded. 146 | Use this if the literal path includes a wildcard character you do not intent to expand. 147 | 148 | ```yaml 149 | Type: String[] 150 | Parameter Sets: Literal 151 | Aliases: PSPath, Name, Filename, Fullname 152 | 153 | Required: True 154 | Position: Named 155 | Default value: None 156 | Accept pipeline input: True (ByPropertyName, ByValue) 157 | Accept wildcard characters: False 158 | ``` 159 | 160 | ### -Path 161 | The full or relative path a single HL7 file or directory. 162 | This may include wildcards in the path name. 163 | If a directory is provide, all files within the directory will be examined. 164 | Exceptions will be raised if a file isn't identified as a HL7 v2.x file. 165 | This parameter accepts a list of files, separate each file file with a ','. 166 | 167 | ```yaml 168 | Type: String[] 169 | Parameter Sets: Path 170 | Aliases: 171 | 172 | Required: True 173 | Position: 0 174 | Default value: None 175 | Accept pipeline input: True (ByPropertyName, ByValue) 176 | Accept wildcard characters: False 177 | ``` 178 | 179 | ### -UpdateAllRepeats 180 | Update all repeating fields identified by -ItemPosition 181 | 182 | ```yaml 183 | Type: SwitchParameter 184 | Parameter Sets: (All) 185 | Aliases: 186 | 187 | Required: False 188 | Position: Named 189 | Default value: None 190 | Accept pipeline input: False 191 | Accept wildcard characters: False 192 | ``` 193 | 194 | ### -Value 195 | The new value to set the item to. 196 | 197 | ```yaml 198 | Type: String 199 | Parameter Sets: (All) 200 | Aliases: 201 | 202 | Required: True 203 | Position: 2 204 | Default value: None 205 | Accept pipeline input: False 206 | Accept wildcard characters: False 207 | ``` 208 | 209 | ### -WhatIf 210 | Shows what would happen if the cmdlet runs. 211 | The cmdlet is not run. 212 | 213 | ```yaml 214 | Type: SwitchParameter 215 | Parameter Sets: (All) 216 | Aliases: wi 217 | 218 | Required: False 219 | Position: Named 220 | Default value: None 221 | Accept pipeline input: False 222 | Accept wildcard characters: False 223 | ``` 224 | 225 | ### -ProgressAction 226 | {{ Fill ProgressAction Description }} 227 | 228 | ```yaml 229 | Type: ActionPreference 230 | Parameter Sets: (All) 231 | Aliases: proga 232 | 233 | Required: False 234 | Position: Named 235 | Default value: None 236 | Accept pipeline input: False 237 | Accept wildcard characters: False 238 | ``` 239 | 240 | ### CommonParameters 241 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 242 | 243 | ## INPUTS 244 | 245 | ### System.String[] 246 | 247 | ## OUTPUTS 248 | 249 | ### System.Object 250 | ## NOTES 251 | 252 | ## RELATED LINKS 253 | 254 | [Online Help](https://github.com/RobHolme/HL7-Powershell-Module#set-hl7item) -------------------------------------------------------------------------------- /install.ps1: -------------------------------------------------------------------------------- 1 | <# Rob Holme 11/07/2024 2 | .SYNOPSIS 3 | Copy the module files from the current folder to the PowerShell module home. 4 | .DESCRIPTION 5 | Copy the module files from the current folder to the PowerShell module home. Ignore dot folders (such as .git). 6 | .PARAMETER Scope 7 | Install the module to either the current user module path, the all users module path, or prompt the user to select a path from the PSModulePath environment variable. 8 | .EXAMPLE 9 | ./Install.ps1 -Scope CurrentUser 10 | .EXAMPLE 11 | ./Install.ps1 -Scope AllUsers 12 | .EXAMPLE 13 | ./Install.ps1 -Scope PromptForModulePath 14 | #> 15 | 16 | [CmdletBinding()] 17 | param ( 18 | [Parameter( 19 | Position = 0, 20 | Mandatory = $False, 21 | ValueFromPipeline = $False, 22 | ValueFromPipelineByPropertyName = $True 23 | )] 24 | [ValidateSet("CurrentUser", "AllUsers", "PromptForModulePath")] 25 | [string] $Scope = "CurrentUser" 26 | ) 27 | 28 | # Get the module version number from the module manifest file. 29 | # Return $null if the module version can not be parsed. 30 | function Get-ModuleVersion() { 31 | $moduleManifestFile = Get-ModuleManifestFile 32 | if ($null -ne $moduleManifestFile) { 33 | $versionElement = Get-Content $moduleManifestFile | Select-String "ModuleVersion(\s){0,}=(\s){0,}('|"")\d{1,}(\.{1}\d{1,}){0,}" 34 | if ($versionElement.Matches.Count -ne 1) { 35 | Write-Error "ModuleVersion element not detected in manifest" 36 | return $null 37 | } 38 | else { 39 | # match version number string 40 | $moduleVersionMatch = ([regex]::Match($versionElement, "\d{1,}(\.{1}\d{1,}){0,}")) 41 | if ($moduleVersionMatch.Success) { 42 | return $moduleVersionMatch[0].Value 43 | } 44 | else { 45 | return $null 46 | } 47 | } 48 | } 49 | return $null 50 | } 51 | 52 | # Get the module manifest file 53 | # Return $null if not found, or more than 1 manaifest file present in the current directory. 54 | function Get-ModuleManifestFile { 55 | try { 56 | $moduleFile = Get-ChildItem *.psd1 57 | } 58 | catch { 59 | Write-Error "Exception raised while detecting module file, exiting. Use -Debug switch to view exception" 60 | Write-Debug $_.Exception 61 | return $null 62 | } 63 | 64 | # Make sure only one module file is found, otherwise exit. 65 | if ($moduleFile.Count -eq 1) { 66 | return $moduleFile 67 | } 68 | else { 69 | Write-Error "Single module manifest file expected, none or more than 1 found." 70 | return $null 71 | } 72 | } 73 | 74 | # Get the module path based on scope and platform 75 | function Get-PSModulePath { 76 | param ( 77 | [Parameter( 78 | Position = 0, 79 | Mandatory = $True, 80 | ValueFromPipeline = $False, 81 | ValueFromPipelineByPropertyName = $False 82 | )] 83 | [ValidateSet("CurrentUser", "AllUsers")] 84 | [string] $moduleScope 85 | ) 86 | 87 | if ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows)) { 88 | if ($IsCoreCLR) { 89 | $powerShellType = "PowerShell" 90 | } 91 | else { 92 | $powerShellType = "WindowsPowerShell" 93 | } 94 | $localUserDir = Join-Path ([Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments)) $powerShellType 95 | $allUsersDir = Join-Path ([Environment]::GetFolderPath([Environment+SpecialFolder]::ProgramFiles)) $powerShellType 96 | 97 | } 98 | else { 99 | # Paths are the same for both Linux and macOS 100 | $localUserDir = Join-Path (Get-HomeFolder) ".local/share/powershell" 101 | $allUsersDir = "/usr/local/share/powershell" 102 | } 103 | if ($moduleScope -eq "AllUsers") { 104 | return $allUsersDir 105 | } 106 | else { 107 | return $localUserDir 108 | } 109 | 110 | } 111 | 112 | # Prompt user to select the module path from existing paths in the PSModulePath environment variable 113 | function Select-PSModulePath { 114 | $pathSeperator = [System.IO.Path]::PathSeparator # windows = ';' linux/mac = ':' 115 | $allModules = $env:PSModulePath -Split $pathSeperator 116 | for ($i = 1; $i -le $allModules.Count; $i++) { 117 | Write-Host "`t($i) .... $($allModules[$i-1])" 118 | } 119 | 120 | 121 | # confirm the repsonse is valid, if not return null. 122 | try { 123 | [Int32]$selection = Read-Host -Prompt "Select install path for module (1 to $($allModules.Count))" 124 | 125 | if (($selection -gt 0) -and ($selection -le $allModules.Count)) { 126 | return $allModules[$selection - 1] 127 | } 128 | else { 129 | Write-Warning "Selection is out of range. Select from 1 to $($allModules.Count)." 130 | return $null 131 | } 132 | } 133 | catch { 134 | Write-Warning "Select a number corresponding to the host to connect to (from 1 to $($allModules.Count))." 135 | return $null 136 | } 137 | } 138 | 139 | # Helper function to get home directory 140 | function Get-HomeFolder { 141 | $envHome = [System.Environment]::GetEnvironmentVariable("HOME") ?? $null 142 | 143 | if ($null -ne $envHome) { 144 | return $envHome 145 | } 146 | # Return an empty string in this case so the process working directory will be used. 147 | else { 148 | return "" 149 | } 150 | } 151 | 152 | function Get-ModuleName { 153 | $moduleManifestFile = Get-ModuleManifestFile 154 | if ($null -ne $moduleManifestFile) { 155 | return $moduleManifestFile.BaseName 156 | } 157 | return $null 158 | } 159 | 160 | 161 | # run the build script to generate the module dlls 162 | ./publish.cmd 163 | 164 | # The module manaifest (and module) is located in the 'module\hl7tools' folder 165 | cd module\hl7tools 166 | 167 | 168 | # Prompt user to select modile if "PromptForModulePath" provided as the -Scope parameter, otherwise use AllUsers or CurrentUser path based on the parameter 169 | # Note: -Scope defaults to CurrentUser if no paramter value provided. 170 | if($scope -eq "PromptForModulePath") { 171 | $moduleRootPath = Select-PSModulePath 172 | } 173 | else { 174 | $moduleRootPath = Join-Path -Path (Get-PSModulePath -moduleScope $Scope) -ChildPath "Modules" 175 | } 176 | 177 | $moduleVersion = Get-ModuleVersion 178 | $moduleName = Get-ModuleName 179 | Write-Verbose "Module name:`t`t $($moduleName)" 180 | Write-Verbose "Module path:`t`t $($moduleRootPath)" 181 | Write-Verbose "Module version:`t $($moduleVersion)" 182 | 183 | if (($null -ne $moduleVersion) -and ($null -ne $moduleRootPath) -and ($null -ne $moduleName)) { 184 | # construct the module path ($env:psmodulepath\modulename\version) 185 | $destinationPath = Join-Path -Path $moduleRootPath -ChildPath $moduleName -AdditionalChildPath $moduleVersion 186 | # create the folder if it does not already exist 187 | if (!(Test-Path -Path $destinationPath)) { 188 | Write-Verbose "Creating folder $destinationPath" 189 | try { 190 | New-Item $destinationPath -ItemType Directory -ErrorAction Stop| Out-Null 191 | } 192 | catch { 193 | Write-Error "Unable to create directory $destinationPath. Use -Debug switch for details" 194 | Write-Debug $_.Exception 195 | exit 196 | } 197 | } 198 | # copy all files, exclude dot folders (e.g. .git) 199 | Write-Host "Installing module to $destinationPath" 200 | try { 201 | Copy-Item -Path (Join-Path -Path (Get-Location).Path -ChildPath '*') -Recurse -Destination $destinationPath.ToString() -Exclude '.*','images' -Force -ErrorAction Stop 202 | } 203 | catch { 204 | Write-Error "Unable to copy files to $destinationPath. Use -Debug switch for details" 205 | Write-Debug $_.Exception 206 | exit 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /module/hl7tools/hl7tools.format.ps1xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SelectHL7ItemResult 7 | 8 | HL7Tools.SelectHL7ItemResult.old 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | If ($_.ItemValue.Count -eq 1){$_.ItemValue[0]}else{$_.ItemValue} 25 | 26 | 27 | Filename 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | SelectHL7ItemResultList 37 | 38 | HL7Tools.SelectHL7ItemResult.old 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | If ($_.ItemValue.Count -eq 1){$_.ItemValue[0]}else{$_.ItemValue} 47 | 48 | 49 | 50 | FileName 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | SendHL7MessageResultList 60 | 61 | HL7Tools.SendHL7MessageResult 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | Status 70 | 71 | 72 | 73 | TimeSent 74 | 75 | 76 | 77 | ACKMessage 78 | 79 | 80 | 81 | Filename 82 | 83 | 84 | 85 | ElapsedSeconds 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | SetHL7ItemResult 95 | 96 | HL7Tools.SetHL7ItemResult 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | HL7Item 118 | 119 | 120 | NewValue 121 | 122 | 123 | OldValue 124 | 125 | 126 | Filename 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | RemoveHL7ItemResult 137 | 138 | HL7Tools.RemoveHL7ItemResult 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | HL7Item 157 | 158 | 159 | DeletedValue 160 | 161 | 162 | Filename 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | ReceivedMessageResult 173 | 174 | HL7Tools.ReceivedMessageResult 175 | 176 | 177 | 178 | 179 | 180 | 20 181 | 182 | 183 | 184 | 22 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | Trigger 195 | 196 | 197 | RemoteConnection 198 | 199 | 200 | Filename 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | ShowHL7MessageTimelineResult 211 | 212 | HL7Tools.ShowHL7MessageTimelineResult 213 | 214 | 215 | 216 | 217 | 218 | 25 219 | 220 | 221 | 222 | 16 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | MessageTimestamp 233 | 234 | 235 | MessageTrigger 236 | 237 | 238 | $split=$_.Filename.Split("\");$split[$split.length -1] 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | -------------------------------------------------------------------------------- /src/RemoveHL7Identifiers.cs: -------------------------------------------------------------------------------- 1 | /* Filename: RemoveHL7Identifiers.cs 2 | * 3 | * Author: Rob Holme (rob@holme.com.au) 4 | * 5 | * Code to handle the Path and LiteralPath parameter sets, and expansion of wildcards is based 6 | * on Oisin Grehan's post: http://www.nivot.org/blog/post/2008/11/19/Quickstart1ACmdletThatProcessesFilesAndDirectories 7 | * 8 | * Date: 21/07/2016 - Intial version, masks predefined list of fields only. 9 | * 31/07/2016 - Implemented custom list of items to mask. 10 | * 11 | * Notes: Implements the cmdlet to mask out personaly identifiable information from HL7 v2 Messages 12 | * 13 | */ 14 | 15 | 16 | namespace HL7Tools 17 | { 18 | using System; 19 | using System.IO; 20 | using System.Text; 21 | using System.Collections.Generic; 22 | using System.Management.Automation; 23 | 24 | /// 25 | /// Class implementing the cmdlet Remove-HL7Identifiers 26 | /// 27 | [Cmdlet(VerbsCommon.Remove, "HL7Identifiers", SupportsShouldProcess = true)] 28 | public class RemoveHL7Identifiers : PSCmdlet 29 | { 30 | private char maskChar = '*'; 31 | private bool overwriteFile = false; 32 | private string[] paths; 33 | private bool expandWildcards = false; 34 | private string[] customItemsList = new string[] { }; 35 | private string encoding = "UTF-8"; 36 | 37 | // Parameter set for the -Path and -LiteralPath parameters. A parameter set ensures these options are mutually exclusive. 38 | // A LiteralPath is used in situations where the filename actually contains wild card characters (eg File[1-10].txt) and you want 39 | // to use the literal file name instead of treating it as a wildcard search. 40 | [Parameter( 41 | Mandatory = true, 42 | ValueFromPipeline = false, 43 | ValueFromPipelineByPropertyName = true, 44 | ParameterSetName = "Literal") 45 | ] 46 | [Alias("PSPath", "Name", "Filename")] 47 | [ValidateNotNullOrEmpty] 48 | public string[] LiteralPath 49 | { 50 | get { return this.paths; } 51 | set { this.paths = value; } 52 | } 53 | 54 | [Parameter( 55 | Position = 0, 56 | Mandatory = true, 57 | ValueFromPipeline = true, 58 | ValueFromPipelineByPropertyName = true, 59 | ParameterSetName = "Path") 60 | ] 61 | [ValidateNotNullOrEmpty] 62 | public string[] Path 63 | { 64 | get { return this.paths; } 65 | set 66 | { 67 | this.expandWildcards = true; 68 | this.paths = value; 69 | } 70 | } 71 | 72 | // A list of HL7 items to mask, supplied by the user 73 | [Parameter( 74 | Mandatory = false, 75 | Position = 1, 76 | HelpMessage = "User supplied list of items to mask" 77 | )] 78 | public string[] CustomItemsList 79 | { 80 | get { return this.customItemsList; } 81 | set { this.customItemsList = value; } 82 | } 83 | 84 | // The mask character to use. Optional, defaults to '*' 85 | [Parameter( 86 | Mandatory = false, 87 | Position = 2, 88 | HelpMessage = "The mask character to use" 89 | )] 90 | public char MaskChar 91 | { 92 | get { return this.maskChar; } 93 | set { this.maskChar = value; } 94 | } 95 | 96 | // Switch to overwrite the original file, instead of writing the masked data to a new file. Optional, defaults to false. 97 | [Parameter( 98 | Mandatory = false, 99 | Position = 3, 100 | HelpMessage = "Switch to overwrite the original file" 101 | )] 102 | public SwitchParameter OverwriteFile 103 | { 104 | get { return this.overwriteFile; } 105 | set { this.overwriteFile = value; } 106 | } 107 | 108 | // Parameter to specify the message character encoding format 109 | [Parameter( 110 | Mandatory = false, 111 | Position = 4, 112 | HelpMessage = "Text encoding ('UTF-8' | 'ISO-8859-1'")] 113 | [ValidateSet("UTF-8", "ISO-8859-1")] 114 | public string Encoding 115 | { 116 | get { return this.encoding; } 117 | set { this.encoding = value; } 118 | } 119 | 120 | /// 121 | /// remove identifying fields 122 | /// 123 | protected override void ProcessRecord() 124 | { 125 | // validate the that the list of locations to mask is valid. 126 | foreach (string item in this.customItemsList) { 127 | // confirm each filter is formatted correctly 128 | if (!Common.IsItemLocationValid(item)) { 129 | ArgumentException ex = new ArgumentException(item + " does not appear to be a valid HL7 location"); 130 | ErrorRecord error = new ErrorRecord(ex, "InvalidFilter", ErrorCategory.InvalidArgument, item); 131 | this.WriteError(error); 132 | return; 133 | } 134 | } 135 | 136 | // set the text encoding 137 | Encoding encoder = System.Text.Encoding.GetEncoding(this.encoding); 138 | WriteVerbose("Encoding: " + encoder.EncodingName); 139 | 140 | foreach (string path in paths) { 141 | // This will hold information about the provider containing the items that this path string might resolve to. 142 | ProviderInfo provider; 143 | 144 | // This will be used by the method that processes literal paths 145 | PSDriveInfo drive; 146 | 147 | // this contains the paths to process for this iteration of the loop to resolve and optionally expand wildcards. 148 | List filePaths = new List(); 149 | 150 | // if the path provided is a directory, expand the files in the directory and add these to the list. 151 | if (Directory.Exists(path)) { 152 | filePaths.AddRange(Directory.GetFiles(path)); 153 | } 154 | 155 | // not a directory, could be a wild-card or literal filepath 156 | else { 157 | if (expandWildcards) { 158 | // Turn *.txt into foo.txt,foo2.txt etc. If path is just "foo.txt," it will return unchanged. If the filepath expands into a directory ignore it. 159 | foreach (string expandedFilePath in this.GetResolvedProviderPathFromPSPath(path, out provider)) { 160 | if (!Directory.Exists(expandedFilePath)) { 161 | filePaths.Add(expandedFilePath); 162 | } 163 | } 164 | } 165 | else { 166 | // no wildcards, so don't try to expand any * or ? symbols. 167 | filePaths.Add(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(path, out provider, out drive)); 168 | } 169 | // ensure that this path (or set of paths after wildcard expansion) 170 | // is on the filesystem. A wildcard can never expand to span multiple providers. 171 | if (Common.IsFileSystemPath(provider, path) == false) { 172 | // no, so skip to next path in paths. 173 | continue; 174 | } 175 | } 176 | 177 | // At this point, we have a list of paths on the filesystem, process each file. 178 | foreach (string filePath in filePaths) { 179 | // If the file does not exist display an error and return. 180 | if (!File.Exists(filePath)) { 181 | FileNotFoundException fileException = new FileNotFoundException("File not found", filePath); 182 | ErrorRecord fileNotFoundError = new ErrorRecord(fileException, "FileNotFound", ErrorCategory.ObjectNotFound, filePath); 183 | WriteError(fileNotFoundError); 184 | return; 185 | } 186 | try { 187 | string fileContents = File.ReadAllText(filePath, encoder); 188 | HL7Message message = new HL7Message(fileContents); 189 | // if a custom list of items is provided, then mask out each nominated item 190 | if (customItemsList.Length > 0) { 191 | foreach (string item in customItemsList) { 192 | message.MaskHL7Item(item, this.maskChar); 193 | } 194 | } 195 | 196 | // otherwise mask out default items 197 | else { 198 | message.DeIdentify(this.maskChar); 199 | } 200 | 201 | char pathSeparator = System.IO.Path.DirectorySeparatorChar; 202 | string newFilename = filePath.Substring(0, filePath.LastIndexOf(pathSeparator) + 1) + "MASKED_" + filePath.Substring(filePath.LastIndexOf(pathSeparator) + 1, filePath.Length - (filePath.LastIndexOf(pathSeparator) + 1)); 203 | 204 | // if the overwrite switch is set, then use the original file name. 205 | if (this.overwriteFile) { 206 | newFilename = filePath; 207 | } 208 | // Write changes to the file. Replace the segment delimeter with the system newline string as this is being written to a file. 209 | string cr = ((char)0x0D).ToString(); 210 | string newline = System.Environment.NewLine; 211 | if (this.ShouldProcess(newFilename, "Saving changes to file")) { 212 | System.IO.File.WriteAllText(newFilename, message.ToString().Replace(cr,newline), encoder); 213 | } 214 | WriteObject("Masked file saved as " + newFilename); 215 | } 216 | 217 | // if the file does not start with a MSH segment, the constructor will throw an exception. 218 | catch (System.ArgumentException) { 219 | ArgumentException argException = new ArgumentException("The file does not appear to be a valid HL7 v2 message", filePath); 220 | ErrorRecord fileNotFoundError = new ErrorRecord(argException, "FileNotValid", ErrorCategory.InvalidData, filePath); 221 | WriteError(fileNotFoundError); 222 | return; 223 | } 224 | } 225 | } 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/SelectHL7Item.cs: -------------------------------------------------------------------------------- 1 | /* Filename: SelectHl7Item.cs 2 | * 3 | * Author: Rob Holme (rob@holme.com.au) 4 | * 5 | * Credits: Code to handle the Path and LiteralPath parameter sets, and expansion of wildcards is based 6 | * on Oisin Grehan's post: http://www.nivot.org/blog/post/2008/11/19/Quickstart1ACmdletThatProcessesFilesAndDirectories 7 | * 8 | * Date: 21/07/2016 9 | * 10 | * Notes: Implements the cmdlet to retrieve a specific item from a HL7 v2 message. 11 | * 12 | */ 13 | 14 | // TO DO: create help XML file https://msdn.microsoft.com/en-us/library/bb525433(v=vs.85).aspx 15 | 16 | namespace HL7Tools { 17 | using System; 18 | using System.Collections.Generic; 19 | using System.IO; 20 | using System.Management.Automation; 21 | using System.Text; 22 | 23 | // CmdLet: Select-HL7Item 24 | // Returns a specific item from the message based on the location 25 | [Cmdlet(VerbsCommon.Select, "HL7Item")] 26 | public class SelectHL7Item : PSCmdlet { 27 | private string[] itemPosition; 28 | private string[] paths; 29 | private bool expandWildcards = false; 30 | private string[] filter = new string[] { }; 31 | private bool filterConditionsMet = true; 32 | private string encoding = "UTF-8"; 33 | private bool abortProcessing = false; 34 | 35 | // Parameter set for the -Path and -LiteralPath parameters. A parameter set ensures these options are mutually exclusive. 36 | // A LiteralPath is used in situations where the filename actually contains wild card characters (eg File[1-10].txt) and you want 37 | // to use the literal file name instead of treating it as a wildcard search. 38 | [Parameter( 39 | Mandatory = true, 40 | ValueFromPipeline = false, 41 | ValueFromPipelineByPropertyName = true, 42 | ParameterSetName = "Literal") 43 | ] 44 | [Alias("PSPath", "Name", "Filename")] 45 | [ValidateNotNullOrEmpty] 46 | public string[] LiteralPath { 47 | get { return this.paths; } 48 | set { this.paths = value; } 49 | } 50 | 51 | [Parameter( 52 | Position = 0, 53 | Mandatory = true, 54 | ValueFromPipeline = true, 55 | ValueFromPipelineByPropertyName = true, 56 | ParameterSetName = "Path") 57 | 58 | ] 59 | [ValidateNotNullOrEmpty] 60 | public string[] Path { 61 | get { return this.paths; } 62 | set { 63 | this.expandWildcards = true; 64 | this.paths = value; 65 | } 66 | } 67 | 68 | // A parameter for position of the item to return 69 | [Parameter( 70 | Mandatory = true, 71 | Position = 1, 72 | HelpMessage = "Position of the item(s) to return, e.g. PID-3.1" 73 | )] 74 | [Alias("Item")] 75 | public string[] ItemPosition { 76 | get { return this.itemPosition; } 77 | set { this.itemPosition = value; } 78 | } 79 | 80 | // Parameter to optionally filter the messages based on matching message contents 81 | [Parameter( 82 | Mandatory = false, 83 | Position = 2, 84 | HelpMessage = "Filter on message contents")] 85 | public string[] Filter { 86 | get { return this.filter; } 87 | set { this.filter = value; } 88 | } 89 | 90 | // Parameter to specify the message character encoding format 91 | [Parameter( 92 | Mandatory = false, 93 | Position = 3, 94 | HelpMessage = "Text encoding ('UTF-8' | 'ISO-8859-1'")] 95 | [ValidateSet("UTF-8", "ISO-8859-1")] 96 | public string Encoding { 97 | get { return this.encoding; } 98 | set { this.encoding = value; } 99 | } 100 | 101 | /// 102 | /// Powershell BeginProcessing. Validate paremeters are formatted correctly. 103 | /// 104 | protected override void BeginProcessing() { 105 | base.BeginProcessing(); 106 | 107 | // confirm the item location parameter is valid before processing any files 108 | foreach (string position in this.itemPosition) { 109 | if (!Common.IsItemLocationValid(position)) { 110 | ArgumentException ex = new ArgumentException(position + " does not appear to be a valid HL7 item possition"); 111 | ErrorRecord error = new ErrorRecord(ex, "InvalidElement", ErrorCategory.InvalidArgument, position); 112 | this.WriteError(error); 113 | abortProcessing = true; 114 | return; 115 | } 116 | } 117 | 118 | // confirm the filter parameter is valid before processing any files 119 | foreach (string currentFilter in this.filter) { 120 | // confirm each filter is formatted correctly 121 | if (!Common.IsFilterValid(currentFilter)) { 122 | ArgumentException ex = new ArgumentException(currentFilter + " does not appear to be a valid filter"); 123 | ErrorRecord error = new ErrorRecord(ex, "InvalidFilter", ErrorCategory.InvalidArgument, currentFilter); 124 | this.WriteError(error); 125 | abortProcessing = true; 126 | return; 127 | } 128 | } 129 | } 130 | 131 | 132 | /// 133 | /// get the HL7 item provided via the cmdlet parameter HL7ItemPosition 134 | /// 135 | protected override void ProcessRecord() { 136 | base.ProcessRecord(); 137 | 138 | if (abortProcessing) { 139 | return; 140 | } 141 | 142 | // set the text encoding 143 | Encoding encoder = System.Text.Encoding.GetEncoding(this.encoding); 144 | WriteVerbose("Encoding: " + encoder.EncodingName); 145 | 146 | // expand the file or directory information provided in the -Path or -LiteralPath parameters 147 | foreach (string path in paths) { 148 | // This will hold information about the provider containing the items that this path string might resolve to. 149 | ProviderInfo provider; 150 | 151 | // This will be used by the method that processes literal paths 152 | PSDriveInfo drive; 153 | 154 | // this contains the paths to process for this iteration of the loop to resolve and optionally expand wildcards. 155 | List filePaths = new List(); 156 | 157 | // if the path provided is a directory, expand the files in the directory and add these to the list. 158 | if (Directory.Exists(path)) { 159 | filePaths.AddRange(Directory.GetFiles(path)); 160 | } 161 | 162 | // not a directory, could be a wildcard or literal filepath 163 | else { 164 | // expand wildcards. This assumes if the user listed a directory it is literal 165 | if (expandWildcards) { 166 | // Turn *.txt into foo.txt,foo2.txt etc. If path is just "foo.txt," it will return unchanged. If the filepath expands into a directory ignore it. 167 | foreach (string expandedFilePath in this.GetResolvedProviderPathFromPSPath(path, out provider)) { 168 | if (!Directory.Exists(expandedFilePath)) { 169 | filePaths.Add(expandedFilePath); 170 | } 171 | } 172 | } 173 | else { 174 | // no wildcards, so don't try to expand any * or ? symbols. 175 | filePaths.Add(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(path, out provider, out drive)); 176 | } 177 | // ensure that this path (or set of paths after wildcard expansion) 178 | // is on the filesystem. A wildcard can never expand to span multiple providers. 179 | if (Common.IsFileSystemPath(provider, path) == false) { 180 | // no, so skip to next path in paths. 181 | continue; 182 | } 183 | } 184 | 185 | // At this point, we have a list of paths on the filesystem, process each file. 186 | foreach (string filePath in filePaths) { 187 | // If the file does not exist display an error and return. 188 | if (!File.Exists(filePath)) { 189 | FileNotFoundException fileException = new FileNotFoundException("File not found", filePath); 190 | ErrorRecord fileNotFoundError = new ErrorRecord(fileException, "FileNotFound", ErrorCategory.ObjectNotFound, filePath); 191 | WriteError(fileNotFoundError); 192 | return; 193 | } 194 | 195 | // process the message 196 | try { 197 | // assume the filter is true, until a failed match is found 198 | this.filterConditionsMet = true; 199 | // load the file into a HL7Message object for processing 200 | string fileContents = File.ReadAllText(filePath, encoder); 201 | HL7Message message = new HL7Message(fileContents); 202 | // if a filter was supplied, evaluate if the file matches the filter condition 203 | if (this.filter != null) { 204 | // check to see is all of the filter conditions are met (ie AND all filters supplied). 205 | foreach (string currentFilter in this.filter) { 206 | bool anyItemMatch = false; 207 | string filterItem = Common.GetFilterItem(currentFilter); 208 | string filterValue = Common.GetFilterValue(currentFilter); 209 | // for repeating fields, only one of the items returned has to match for the filter to be evaluated as true. 210 | foreach (string itemValue in message.GetHL7ItemValue(filterItem)) { 211 | // convert both values to upper case for a case insentive match 212 | if (itemValue.ToUpper() == filterValue.ToUpper()) { 213 | anyItemMatch = true; 214 | } 215 | } 216 | // if none of the repeating field items match, then fail the filter match for this file. 217 | if (!anyItemMatch) { 218 | this.filterConditionsMet = false; 219 | } 220 | } 221 | } 222 | 223 | // if the filter supplied matches this message (or no filter provided) then process the file to optain the HL7 item requested 224 | if (filterConditionsMet) { 225 | // create a PSObject 226 | PSObject result = new PSObject(); 227 | foreach (string hl7ItemPosition in itemPosition) { 228 | string[] hl7Items = message.GetHL7ItemValue(hl7ItemPosition); 229 | // if the hl7Items array is empty, the item was not found in the message. Write null property otherwise powershell will omit columns in output if firest value does nto include all properties 230 | if (hl7Items.Length == 0) { 231 | result.Properties.Add(new PSNoteProperty(hl7ItemPosition.ToString(), null)); 232 | WriteVerbose("Item " + hl7ItemPosition + " not found in the message " + filePath); 233 | } 234 | // items were returned, add properties to PSObject 235 | else { 236 | // if only a single value returned, save it as a string, not a string[] 237 | if (hl7Items.Length == 1) { 238 | result.Properties.Add(new PSNoteProperty(hl7ItemPosition.ToString(), hl7Items[0])); 239 | } 240 | else { 241 | result.Properties.Add(new PSNoteProperty(hl7ItemPosition.ToString(), hl7Items)); 242 | } 243 | } 244 | } 245 | result.Properties.Add(new PSNoteProperty("Filename", filePath)); 246 | WriteObject(result); 247 | } 248 | } 249 | 250 | // if the file does not start with a MSH segment, the constructor will throw an exception. 251 | catch (System.ArgumentException) { 252 | ArgumentException argException = new ArgumentException("The file does not appear to be a valid HL7 v2 message", filePath); 253 | ErrorRecord invalidFileError = new ErrorRecord(argException, "FileNotValid", ErrorCategory.InvalidData, filePath); 254 | WriteError(invalidFileError); 255 | return; 256 | } 257 | } 258 | } 259 | } 260 | } 261 | 262 | 263 | } 264 | -------------------------------------------------------------------------------- /src/SplitHL7BatchFile.cs: -------------------------------------------------------------------------------- 1 | /* Filename: SplitHL7BatchFile.cs 2 | * 3 | * Author: Rob Holme (rob@holme.com.au) 4 | * 5 | * Date: 24/10/2016 6 | * 7 | * Notes: Implements the CmdLet Split-HL7Batchfile. Splits batch mode HL7 messages from a single bacth file 8 | * into multiple files. 9 | * 10 | */ 11 | 12 | namespace HL7Tools 13 | { 14 | using System.Collections.Generic; 15 | using System.IO; 16 | using System.Linq; 17 | using System.Text.RegularExpressions; 18 | using System.Management.Automation; 19 | 20 | // CmdLet: Update-HL7Item 21 | // Replaces the value of a specific item from the message 22 | [Cmdlet(VerbsCommon.Split, "HL7BatchFile", SupportsShouldProcess = true)] 23 | public class SplitHl7BatchFile : PSCmdlet 24 | { 25 | private string[] paths; 26 | private bool expandWildcards = false; 27 | private bool overwriteFile; 28 | private bool yesToAll; 29 | private bool noToAll; 30 | 31 | // Parameter set for the -Path and -LiteralPath parameters. A parameter set ensures these options are mutually exclusive. 32 | // A LiteralPath is used in situations where the filename actually contains wild card characters (eg File[1-10].txt) and you want 33 | // to use the literaral file name instead of treating it as a wildcard search. 34 | [Parameter( 35 | Mandatory = true, 36 | ValueFromPipeline = true, 37 | ValueFromPipelineByPropertyName = true, 38 | ParameterSetName = "Literal") 39 | ] 40 | [Alias("PSPath", "Name", "Filename", "Fullname")] 41 | [ValidateNotNullOrEmpty] 42 | public string[] LiteralPath 43 | { 44 | get { return this.paths; } 45 | set { this.paths = value; } 46 | } 47 | 48 | [Parameter( 49 | Position = 0, 50 | Mandatory = true, 51 | ValueFromPipeline = true, 52 | ValueFromPipelineByPropertyName = true, 53 | ParameterSetName = "Path") 54 | 55 | ] 56 | [ValidateNotNullOrEmpty] 57 | public string[] Path 58 | { 59 | get { return this.paths; } 60 | set 61 | { 62 | this.expandWildcards = true; 63 | this.paths = value; 64 | } 65 | } 66 | 67 | // Switch to supress warnings if a file is overwritten by the CmdLet 68 | [Parameter( 69 | Mandatory = false, 70 | HelpMessage = "Do not warn if overwriting existing files?" 71 | )] 72 | [Alias("Overwrite", "Force")] 73 | public SwitchParameter OverwriteFile 74 | { 75 | get { return this.overwriteFile; } 76 | set { this.overwriteFile = value; } 77 | } 78 | 79 | /// 80 | /// Process each file from the pipeline 81 | /// 82 | protected override void ProcessRecord() 83 | { 84 | // expand the file or directory information provided in the -Path or -LiteralPath parameters 85 | foreach (string path in paths) 86 | { 87 | // This will hold information about the provider containing the items that this path string might resolve to. 88 | ProviderInfo provider; 89 | 90 | // This will be used by the method that processes literal paths 91 | PSDriveInfo drive; 92 | 93 | // this contains the paths to process for this iteration of the loop to resolve and optionally expand wildcards. 94 | List filePaths = new List(); 95 | 96 | // if the path provided is a directory, return an error. 97 | if (Directory.Exists(path)) 98 | { 99 | IOException fileException = new FileNotFoundException("The path provided is a directory, not a file", path); 100 | ErrorRecord fileNotFoundError = new ErrorRecord(fileException, "SaveFailed", ErrorCategory.OpenError, path); 101 | WriteError(fileNotFoundError); 102 | return; 103 | } 104 | 105 | // not a directory, could be a wildcard or literal filepath 106 | else 107 | { 108 | // expand wildcards. This assumes if the user listed a directory it is literal 109 | if (expandWildcards) 110 | { 111 | // Turn *.txt into foo.txt,foo2.txt etc. If path is just "foo.txt," it will return unchanged. If the filepath expands into a directory ignore it. 112 | foreach (string expandedFilePath in this.GetResolvedProviderPathFromPSPath(path, out provider)) 113 | { 114 | if (!Directory.Exists(expandedFilePath)) 115 | { 116 | filePaths.Add(expandedFilePath); 117 | } 118 | } 119 | } 120 | else 121 | { 122 | // no wildcards, so don't try to expand any * or ? symbols. 123 | filePaths.Add(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(path, out provider, out drive)); 124 | } 125 | // ensure that this path (or set of paths after wildcard expansion) 126 | // is on the filesystem. A wildcard can never expand to span multiple providers. 127 | if (Common.IsFileSystemPath(provider, path) == false) 128 | { 129 | // no, so skip to next path in paths. 130 | continue; 131 | } 132 | } 133 | 134 | // At this point, we have a list of paths on the filesystem, process each file. 135 | foreach (string filePath in filePaths) 136 | { 137 | // If the file does not exist display an error and return. 138 | if (!File.Exists(filePath)) 139 | { 140 | FileNotFoundException fileException = new FileNotFoundException("File not found", filePath); 141 | ErrorRecord fileNotFoundError = new ErrorRecord(fileException, "FileNotFound", ErrorCategory.ObjectNotFound, filePath); 142 | WriteError(fileNotFoundError); 143 | return; 144 | } 145 | 146 | // process the message 147 | int fileCount = 0; 148 | string newFilePath; 149 | 150 | string tempHL7Message = string.Empty; 151 | 152 | // get the file contents, split on carriage return to return each line. If the file contains end of line characters, the convert to only (as per HL7 spec). 153 | string[] fileContents = File.ReadAllText(filePath).Replace("\r\n", "\r").Split('\r'); 154 | foreach (string currentLine in fileContents) 155 | { 156 | // ignore the file and batch headers/footers 157 | if ((Regex.IsMatch(currentLine, "^FHS", RegexOptions.IgnoreCase)) || (Regex.IsMatch(currentLine, "^BHS", RegexOptions.IgnoreCase)) || (Regex.IsMatch(currentLine, "^BTS", RegexOptions.IgnoreCase)) || (Regex.IsMatch(currentLine, "^FTS", RegexOptions.IgnoreCase))) 158 | { 159 | WriteVerbose("Batch file header/footer detected"); 160 | } 161 | else 162 | { 163 | if (Regex.IsMatch(currentLine, "^MSH", RegexOptions.IgnoreCase)) 164 | { 165 | // special case for the first MSH segment 166 | if (fileCount == 0) 167 | { 168 | tempHL7Message = currentLine; 169 | fileCount++; 170 | } 171 | else 172 | { 173 | // save the changes to a new file (append unique id and message details to original filename) 174 | string msgDetails = ConstructMessageDetails(tempHL7Message); 175 | newFilePath = AppendFilenameSuffix(filePath, fileCount.ToString() + msgDetails); 176 | this.SaveFile(newFilePath, tempHL7Message); 177 | // start storing the next message 178 | fileCount++; 179 | tempHL7Message = currentLine; 180 | } 181 | } 182 | else 183 | { 184 | tempHL7Message += "\r" + currentLine; 185 | } 186 | } 187 | } 188 | // save the last message 189 | // save the changes to a new file (append unique id and message details to original filename) 190 | string lastMsgDetails = ConstructMessageDetails(tempHL7Message); 191 | newFilePath = AppendFilenameSuffix(filePath, fileCount.ToString() + lastMsgDetails); 192 | this.SaveFile(newFilePath, tempHL7Message); 193 | } 194 | } 195 | } 196 | 197 | /// 198 | /// Save the hl7 message to a file 199 | /// 200 | /// 201 | /// 202 | private void SaveFile(string Filename, string Hl7Message) 203 | { 204 | SplitHL7BatchFileResult result; 205 | 206 | // if the -WhatIf switch is supplied don't commit changes to file 207 | if (this.ShouldProcess(Filename, "Saving HL7 message to file")) 208 | { 209 | string cr = ((char)0x0D).ToString(); 210 | string newline = System.Environment.NewLine; 211 | 212 | try 213 | { 214 | // prompt the user to overwrite the file if it exists (and the -OverwriteFile switch is not set) 215 | if (!this.overwriteFile && File.Exists(Filename)) 216 | { 217 | if (this.ShouldContinue("File " + Filename + " exists. Are you sure you want to overwrite the file?", "Overwrite file", ref this.yesToAll, ref this.noToAll)) 218 | { 219 | // replace segment delimeter with system newline char(s) since writing to file. 220 | System.IO.File.WriteAllText(Filename, Hl7Message.Replace(cr, newline)); 221 | result = new SplitHL7BatchFileResult(Filename); 222 | WriteObject(result); 223 | } 224 | } 225 | else 226 | { 227 | // replace segment delimeter with system newline char(s) since writing to file. 228 | System.IO.File.WriteAllText(Filename, Hl7Message.Replace(cr, newline)); 229 | result = new SplitHL7BatchFileResult(Filename); 230 | WriteObject(result); 231 | } 232 | 233 | } 234 | // write error if any exceptions raised when saving the file 235 | catch 236 | { 237 | IOException fileException = new FileNotFoundException("File not found", Filename); 238 | ErrorRecord fileNotFoundError = new ErrorRecord(fileException, "SaveFailed", ErrorCategory.WriteError, Filename); 239 | WriteError(fileNotFoundError); 240 | } 241 | } 242 | } 243 | 244 | /// 245 | /// Append a suffix to a file path. The suffix is added between the filename and extention. 246 | /// 247 | /// 248 | /// 249 | /// 250 | private string AppendFilenameSuffix(string FilePath, string fileSuffix) 251 | { 252 | string newFilePath; 253 | string pathName = System.IO.Path.GetDirectoryName(FilePath); 254 | string fileName = System.IO.Path.GetFileNameWithoutExtension(FilePath); 255 | string fileExtension = System.IO.Path.GetExtension(FilePath); 256 | 257 | newFilePath = System.IO.Path.Combine(pathName, fileName + "_" + fileSuffix); 258 | if (fileExtension.Length > 0) 259 | { 260 | newFilePath = newFilePath + fileExtension; 261 | } 262 | return newFilePath; 263 | } 264 | 265 | private string ConstructMessageDetails(string Hl7Message) 266 | { 267 | HL7Message message = new HL7Message(Hl7Message); 268 | string[] msgID = message.GetHL7ItemValue("MSH-10"); 269 | string[] msgType = message.GetHL7ItemValue("MSH-9.1"); 270 | string[] msgTrigger = message.GetHL7ItemValue("MSH-9.2"); 271 | string msgDetailString = ""; 272 | 273 | // construct the strings if the elements exist in the message 274 | if (!((msgID == null) || (msgID.Length == 0))) 275 | { 276 | msgDetailString += "_" + msgID[0]; 277 | } 278 | if (!((msgType == null) || (msgType.Length == 0))) 279 | { 280 | msgDetailString += "_" + msgType[0]; 281 | } 282 | if (!((msgTrigger == null) || (msgTrigger.Length == 0))) 283 | { 284 | msgDetailString += "_" + msgTrigger[0]; 285 | } 286 | return msgDetailString; 287 | } 288 | } 289 | 290 | /// 291 | /// An object containing the results to be returned to the pipeline. 292 | /// 293 | public class SplitHL7BatchFileResult 294 | { 295 | private string newFilename; 296 | 297 | /// 298 | /// The location of the HL7 item that was changed. e.g. PID-3.1 299 | /// 300 | public string Filename 301 | { 302 | get { return this.newFilename; } 303 | set { this.newFilename = value; } 304 | } 305 | 306 | /// 307 | /// 308 | /// 309 | /// 310 | /// 311 | public SplitHL7BatchFileResult(string NewFilename) 312 | { 313 | this.newFilename = NewFilename; 314 | } 315 | } 316 | } 317 | 318 | 319 | -------------------------------------------------------------------------------- /src/ShowHL7MessageTimeline.cs: -------------------------------------------------------------------------------- 1 | /* Filename: SelectHl7Item.cs 2 | * 3 | * Author: Rob Holme (rob@holme.com.au) 4 | * 5 | * Credits: Code to handle the Path and LiteralPath parameter sets, and expansion of wildcards is based 6 | * on Oisin Grehan's post: http://www.nivot.org/blog/post/2008/11/19/Quickstart1ACmdletThatProcessesFilesAndDirectories 7 | * 8 | * Date: 15/06/2017 9 | * 10 | * Notes: Orders a group of messages chronologically based on the MSH-7 (Received DateTime) field. 11 | * 12 | */ 13 | 14 | namespace HL7Tools 15 | { 16 | 17 | using System; 18 | using System.Collections.Generic; 19 | using System.IO; 20 | using System.Linq; 21 | using System.Management.Automation; 22 | using System.Text.RegularExpressions; 23 | 24 | // CmdLet: Show-HL7MessageTimeline 25 | // Returns a specific item from the message based on the location 26 | [Cmdlet(VerbsCommon.Show, "HL7MessageTimeline")] 27 | public class ShowHL7MessageTimeline : PSCmdlet 28 | { 29 | private string[] paths; 30 | private bool expandWildcards = false; 31 | private bool descending = false; 32 | List messageTimestampResults = new List(); 33 | 34 | // Parameter set for the -Path and -LiteralPath parameters. A parameter set ensures these options are mutually exclusive. 35 | // A LiteralPath is used in situations where the filename actually contains wild card characters (eg File[1-10].txt) and you want 36 | // to use the literaral file name instead of treating it as a wildcard search. 37 | [Parameter( 38 | Mandatory = true, 39 | ValueFromPipeline = false, 40 | ValueFromPipelineByPropertyName = true, 41 | ParameterSetName = "Literal") 42 | ] 43 | [Alias("PSPath", "Name", "Filename")] 44 | [ValidateNotNullOrEmpty] 45 | public string[] LiteralPath 46 | { 47 | get { return this.paths; } 48 | set { this.paths = value; } 49 | } 50 | 51 | [Parameter( 52 | Position = 0, 53 | Mandatory = true, 54 | ValueFromPipeline = true, 55 | ValueFromPipelineByPropertyName = true, 56 | ParameterSetName = "Path") 57 | 58 | ] 59 | [ValidateNotNullOrEmpty] 60 | public string[] Path 61 | { 62 | get { return this.paths; } 63 | set 64 | { 65 | this.expandWildcards = true; 66 | this.paths = value; 67 | } 68 | } 69 | 70 | // Switch to order the message timestamps in descending order instead 71 | [Parameter( 72 | Mandatory = false, 73 | HelpMessage = "Order messages in descending chronological order" 74 | )] 75 | [Alias("Desc")] 76 | public SwitchParameter Descending 77 | { 78 | get { return this.descending; } 79 | set { this.descending = value; } 80 | } 81 | 82 | /// 83 | /// get the HL7 item provided via the cmdlet parameter HL7ItemPosition 84 | /// 85 | protected override void ProcessRecord() 86 | { 87 | 88 | // expand the file or directory information provided in the -Path or -LiteralPath parameters 89 | foreach (string path in paths) { 90 | // This will hold information about the provider containing the items that this path string might resolve to. 91 | ProviderInfo provider; 92 | // This will be used by the method that processes literal paths 93 | PSDriveInfo drive; 94 | // this contains the paths to process for this iteration of the loop to resolve and optionally expand wildcards. 95 | List filePaths = new List(); 96 | 97 | // if the path provided is a directory, expand the files in the directory and add these to the list. 98 | if (Directory.Exists(path)) { 99 | filePaths.AddRange(Directory.GetFiles(path)); 100 | } 101 | 102 | // not a directory, could be a wildcard or literal filepath 103 | else { 104 | // expand wildcards. This assumes if the user listed a directory it is literal 105 | if (expandWildcards) { 106 | // Turn *.txt into foo.txt,foo2.txt etc. If path is just "foo.txt," it will return unchanged. If the filepath expands into a directory ignore it. 107 | foreach (string expandedFilePath in this.GetResolvedProviderPathFromPSPath(path, out provider)) { 108 | if (!Directory.Exists(expandedFilePath)) { 109 | filePaths.Add(expandedFilePath); 110 | } 111 | } 112 | } 113 | else { 114 | // no wildcards, so don't try to expand any * or ? symbols. 115 | filePaths.Add(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(path, out provider, out drive)); 116 | } 117 | // ensure that this path (or set of paths after wildcard expansion) 118 | // is on the filesystem. A wildcard can never expand to span multiple providers. 119 | if (Common.IsFileSystemPath(provider, path) == false) { 120 | // no, so skip to next path in paths. 121 | continue; 122 | } 123 | } 124 | 125 | // At this point, we have a list of paths on the filesystem, process each file. 126 | foreach (string filePath in filePaths) { 127 | // If the file does not exist display an error and return. 128 | if (!File.Exists(filePath)) { 129 | FileNotFoundException fileException = new FileNotFoundException("File not found", filePath); 130 | ErrorRecord fileNotFoundError = new ErrorRecord(fileException, "FileNotFound", ErrorCategory.ObjectNotFound, filePath); 131 | WriteError(fileNotFoundError); 132 | return; 133 | } 134 | 135 | // process the message 136 | try { 137 | // load the file into a HL7Message object for processing 138 | string fileContents = File.ReadAllText(filePath); 139 | HL7Message message = new HL7Message(fileContents); 140 | 141 | // get the message trigger from MSH-9 (only interested in MSH-9.1 and MSH-9.2 - ignore any document types if included). 142 | string[] messageTrigger = message.GetHL7ItemValue("MSH-9"); 143 | string[] splitTrigger = messageTrigger[0].Split(message.ComponentDelimeter); 144 | string simplifiedTrigger; 145 | if (splitTrigger.Length > 1) { 146 | simplifiedTrigger = splitTrigger[0] + message.ComponentDelimeter + splitTrigger[1]; 147 | } 148 | else { 149 | simplifiedTrigger = splitTrigger[0]; 150 | } 151 | 152 | // get the message timestamp 153 | string[] messageDateTime = message.GetHL7ItemValue("MSH-7"); 154 | // if the hl7Items array is empty, the item was not found in the message 155 | if (messageDateTime.Length == 0) { 156 | WriteWarning("MSH-7 does not contain a value for " + filePath); 157 | } 158 | // items were returned 159 | else { 160 | string timePattern = "[0-9]{12}([0-9]{2})?"; 161 | string timezonePattern = "(?<=(/+|-))[0-9]{4}"; 162 | 163 | Match timeMatch = Regex.Match(messageDateTime[0], timePattern); 164 | Match timezoneMatch = Regex.Match(messageDateTime[0], timezonePattern); 165 | 166 | if (timeMatch.Success) { 167 | // time zone is includes 168 | if (timezoneMatch.Success) { 169 | // time includes seconds 170 | if (timeMatch.Value.Length == 14) { 171 | ShowHL7MessageTimelineResult resultListItem = new ShowHL7MessageTimelineResult(DateTime.ParseExact(timeMatch.Value + timezoneMatch.Groups[1].Value + timezoneMatch.Value, "yyyyMMddHHmmsszzzz", null), filePath, simplifiedTrigger); 172 | messageTimestampResults.Add(resultListItem); 173 | } 174 | // time only resolves to minutes 175 | else { 176 | ShowHL7MessageTimelineResult resultListItem = new ShowHL7MessageTimelineResult(DateTime.ParseExact(timeMatch.Value + timezoneMatch.Groups[1].Value + timezoneMatch.Value, "yyyyMMddHHmmzzzz", null), filePath, simplifiedTrigger); 177 | messageTimestampResults.Add(resultListItem); 178 | } 179 | } 180 | // timezone is not included 181 | else { 182 | // time includes seconds 183 | if (timeMatch.Value.Length == 14) { 184 | ShowHL7MessageTimelineResult resultListItem = new ShowHL7MessageTimelineResult(DateTime.ParseExact(timeMatch.Value, "yyyyMMddHHmmss", null), filePath, simplifiedTrigger); 185 | messageTimestampResults.Add(resultListItem); 186 | } 187 | // time only resolves to minutes 188 | else { 189 | ShowHL7MessageTimelineResult resultListItem = new ShowHL7MessageTimelineResult(DateTime.ParseExact(timeMatch.Value, "yyyyMMddHHmm", null), filePath, simplifiedTrigger); 190 | messageTimestampResults.Add(resultListItem); 191 | } 192 | 193 | 194 | } 195 | } 196 | // timestamp missing, or does not resolve down to at least minutes 197 | else { 198 | WriteWarning("Timestamp missing from " + filePath); 199 | } 200 | 201 | } 202 | 203 | } 204 | // if the file does not start with a MSH segment, the constructor will throw an exception. 205 | catch (System.ArgumentException) { 206 | ArgumentException argException = new ArgumentException("The file does not appear to be a valid HL7 v2 message", filePath); 207 | ErrorRecord invalidFileError = new ErrorRecord(argException, "FileNotValid", ErrorCategory.InvalidData, filePath); 208 | WriteError(invalidFileError); 209 | return; 210 | } 211 | } 212 | } 213 | } 214 | 215 | 216 | /// 217 | /// write the results for all files piped into the CmdLet 218 | /// 219 | protected override void EndProcessing() 220 | { 221 | IEnumerable orderedResults; 222 | 223 | // order the list of results by the timestamp property in descending order if the -descending switch is provided 224 | if (this.descending) { 225 | orderedResults = messageTimestampResults.OrderByDescending(ShowHL7MessageTimelineResult => ShowHL7MessageTimelineResult.MessageTimestamp); 226 | } 227 | // default to ordering the timestamps in ascending order if the -descending swithc is not provided 228 | else { 229 | orderedResults = messageTimestampResults.OrderBy(ShowHL7MessageTimelineResult => ShowHL7MessageTimelineResult.MessageTimestamp); 230 | } 231 | foreach (ShowHL7MessageTimelineResult item in orderedResults) { 232 | WriteObject(item); 233 | } 234 | } 235 | } 236 | 237 | /// 238 | /// An object containing the results to be returned to the pipeline 239 | /// 240 | public class ShowHL7MessageTimelineResult 241 | { 242 | private DateTime messageTimestamp; 243 | private string filename; 244 | private string messageTrigger; 245 | 246 | /// 247 | /// The value of the HL7 item 248 | /// 249 | public DateTime MessageTimestamp 250 | { 251 | get { return this.messageTimestamp; } 252 | set { this.messageTimestamp = value; } 253 | } 254 | 255 | /// 256 | /// The filename containing the item returned 257 | /// 258 | public string Filename 259 | { 260 | get { return this.filename; } 261 | set { this.filename = value; } 262 | } 263 | 264 | /// 265 | /// The HL7 message trigger (MSH-9) 266 | /// 267 | public string MessageTrigger 268 | { 269 | get { return this.messageTrigger; } 270 | set { this.messageTrigger = value; } 271 | } 272 | 273 | /// 274 | /// 275 | /// 276 | /// 277 | /// 278 | public ShowHL7MessageTimelineResult(DateTime timestamp, string Filename, string Trigger) 279 | { 280 | this.messageTimestamp = timestamp; 281 | this.filename = Filename; 282 | this.messageTrigger = Trigger; 283 | } 284 | } 285 | 286 | } 287 | -------------------------------------------------------------------------------- /src/HL7TcpListener.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace HL7Tools 3 | { 4 | using System; 5 | using System.Text; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | using System.Threading; 9 | using System.Collections.Concurrent; 10 | 11 | class HL7TCPListener 12 | { 13 | int TCP_TIMEOUT; // timeout value for receiving TCP data in millseconds 14 | private TcpListener tcpListener; 15 | private Thread tcpListenerThread; 16 | private int listenerPort; 17 | private string archivePath = null; 18 | private bool sendACK = true; 19 | private string passthruHost = null; 20 | private int passthruPort; 21 | private string encoding; 22 | private ConcurrentQueue messageQueue = new ConcurrentQueue(); 23 | private bool runThread = true; 24 | private ConcurrentQueue objectQueue; 25 | private ConcurrentQueue warningQueue; 26 | private ConcurrentQueue debugQueue; 27 | 28 | 29 | /// 30 | /// Constructor, includes references to return warnings and result objects 31 | /// 32 | public HL7TCPListener(int port, ref ConcurrentQueue messageQueueRef, ref ConcurrentQueue warningQueueRef, ref ConcurrentQueue verboseQueueRef, int TimeOut = 30000, string MessageEncoding = "UTF-8") 33 | { 34 | this.listenerPort = port; 35 | warningQueue = warningQueueRef; 36 | objectQueue = messageQueueRef; 37 | debugQueue = verboseQueueRef; 38 | TCP_TIMEOUT = TimeOut; 39 | this.encoding = MessageEncoding; 40 | } 41 | 42 | /// 43 | /// Start the TCP listener. Log the options set. 44 | /// 45 | public bool Start() 46 | { 47 | // start a new thread to listen for new TCP connections 48 | this.tcpListener = new TcpListener(IPAddress.Any, this.listenerPort); 49 | this.tcpListenerThread = new Thread(new ThreadStart(StartListener)); 50 | this.tcpListenerThread.Start(); 51 | return true; 52 | } 53 | 54 | /// 55 | /// Stop the all threads 56 | /// 57 | public void RequestStop() 58 | { 59 | this.runThread = false; 60 | this.tcpListener.Stop(); 61 | 62 | } 63 | 64 | /// 65 | /// Start listening for new connections 66 | /// 67 | private void StartListener() 68 | { 69 | try { 70 | this.tcpListener.Start(); 71 | // run the thread unless a request to stop is received 72 | while (this.runThread) { 73 | // waits for a client connection to the listener 74 | TcpClient client = this.tcpListener.AcceptTcpClient(); 75 | this.LogDebug("New client connection accepted from " + client.Client.RemoteEndPoint); 76 | // create a new thread. This will handle communication with a client once connected 77 | Thread clientThread = new Thread(new ParameterizedThreadStart(ReceiveData)); 78 | clientThread.Start(client); 79 | } 80 | } 81 | catch (Exception e) { 82 | LogWarning("An error occurred while attempting to start the listener on port " + this.listenerPort); 83 | LogWarning(e.Message); 84 | LogWarning("HL7Listener exiting."); 85 | } 86 | } 87 | 88 | /// 89 | /// Receive data from a client connection, look for MLLP HL7 message. 90 | /// 91 | /// 92 | private void ReceiveData(object client) 93 | { 94 | // generate a random sequence number to use for the file names 95 | Random random = new Random(Guid.NewGuid().GetHashCode()); 96 | int filenameSequenceStart = random.Next(0, 1000000); 97 | 98 | TcpClient tcpClient = (TcpClient)client; 99 | NetworkStream clientStream = tcpClient.GetStream(); 100 | clientStream.ReadTimeout = TCP_TIMEOUT; 101 | clientStream.WriteTimeout = TCP_TIMEOUT; 102 | 103 | byte[] messageBuffer = new byte[4096]; 104 | int bytesRead; 105 | String messageData = ""; 106 | int messageCount = 0; 107 | 108 | // set the text encoding 109 | Encoding encoder = System.Text.Encoding.GetEncoding(this.encoding); 110 | 111 | while (this.IsRunning() == true) { 112 | bytesRead = 0; 113 | try { 114 | // Wait until a client application submits a message 115 | bytesRead = clientStream.Read(messageBuffer, 0, 4096); 116 | } 117 | catch (Exception) { 118 | // A network error has occurred 119 | LogDebug("Connection from " + tcpClient.Client.RemoteEndPoint + " has ended"); 120 | break; 121 | } 122 | if (bytesRead == 0) { 123 | // The client has disconnected 124 | LogDebug("The client " + tcpClient.Client.RemoteEndPoint + " has disconnected"); 125 | break; 126 | } 127 | // Message buffer received successfully 128 | messageData += encoder.GetString(messageBuffer, 0, bytesRead); 129 | // Find a VT character, this is the beginning of the MLLP frame 130 | int start = messageData.IndexOf((char)0x0B); 131 | if (start >= 0) { 132 | // Search for the end of the MLLP frame (a FS character) 133 | int end = messageData.IndexOf((char)0x1C); 134 | if (end > start) { 135 | messageCount++; 136 | try { 137 | // queue the message to sent to the passthru host if the -PassThru option has been set 138 | if (passthruHost != null) { 139 | messageQueue.Enqueue(messageData.Substring(start + 1, end - (start + 1))); 140 | } 141 | // create a HL7message object from the message received. Use this to access elements needed to populate the ACK message and file name of the archived message 142 | HL7Message message = new HL7Message(messageData.Substring(start + 1, end - (start + 1))); 143 | messageData = ""; // reset the message data string for the next message 144 | string messageTrigger = message.GetHL7ItemValue("MSH-9")[0]; 145 | string messageControlID = message.GetHL7ItemValue("MSH-10")[0]; 146 | //string acceptAckType = message.GetHL7Item("MSH-15")[0]; 147 | string dateStamp = DateTime.Now.Year.ToString() + DateTime.Now.Month.ToString().PadLeft(2,'0') + DateTime.Now.Day.ToString().PadLeft(2, '0') + DateTime.Now.Hour.ToString().PadLeft(2, '0') + DateTime.Now.Minute.ToString().PadLeft(2, '0'); 148 | string filename = dateStamp + "_" + (filenameSequenceStart + messageCount).ToString("D6") + "_" + messageTrigger + ".hl7"; // increment sequence number for each filename 149 | // Write the HL7 message to file. 150 | WriteMessagetoFile(message.ToString(), System.IO.Path.Combine(this.archivePath,filename)); 151 | ReceivedMessageResult resultObject = new ReceivedMessageResult(messageTrigger, System.IO.Path.Combine(this.archivePath, filename), tcpClient.Client.RemoteEndPoint.ToString()); 152 | objectQueue.Enqueue(resultObject); 153 | // send ACK message is MSH-15 is set to AL and ACKs not disbaled by -NOACK command line switch 154 | //if ((this.sendACK) && (acceptAckType.ToUpper() == "AL")) 155 | if (this.sendACK) { 156 | LogDebug("Sending ACK (Message Control ID: " + messageControlID + ")"); 157 | // generate ACK Message and send in response to the message received 158 | string response = GenerateACK(message.ToString()); // TO DO: send ACKS if set in message header, or specified on command line 159 | byte[] encodedResponse = encoder.GetBytes(response); 160 | // Send response 161 | try { 162 | clientStream.Write(encodedResponse, 0, encodedResponse.Length); 163 | clientStream.Flush(); 164 | } 165 | catch (Exception e) { 166 | // A network error has occurred 167 | LogDebug("An error has occurred while sending an ACK to the client " + tcpClient.Client.RemoteEndPoint); 168 | LogDebug(e.Message); 169 | break; 170 | } 171 | } 172 | } 173 | catch (Exception e) { 174 | messageData = ""; // reset the message data string for the next message 175 | LogWarning("An exception occurred while parsing the HL7 message"); 176 | LogWarning(e.Message); 177 | break; 178 | } 179 | } 180 | } 181 | } 182 | LogDebug("Total messages received:" + messageCount); 183 | clientStream.Close(); 184 | clientStream.Dispose(); 185 | tcpClient.Close(); 186 | } 187 | 188 | /// 189 | /// /// 190 | /// Write the HL7 message received to file. Optionally provide the file path, otherwise use the working directory. 191 | /// 192 | /// 193 | /// 194 | private void WriteMessagetoFile(string message, string filename) 195 | { 196 | string cr = ((char)0x0D).ToString(); 197 | string newline = System.Environment.NewLine; 198 | 199 | // write the HL7 message to file. replace segment delimeter with system newline char(s) since writing to file. 200 | try { 201 | LogDebug("Received message. Saving to file " + filename); 202 | System.IO.StreamWriter file = new System.IO.StreamWriter(filename); 203 | file.Write(message.Replace(cr,newline)); 204 | file.Close(); 205 | } 206 | catch (Exception e) { 207 | LogWarning("Failed to write file " + filename); 208 | LogWarning(e.Message); 209 | } 210 | } 211 | 212 | /// 213 | /// Generate a string containing the ACK message in response to the original message. Supply a string containing the original message (or at least the MSH segment). 214 | /// 215 | /// 216 | string GenerateACK(string originalMessage) 217 | { 218 | // create a HL7Message object using the original message as the source to obtain details to reflect back in the ACK message 219 | HL7Message tmpMsg = new HL7Message(originalMessage); 220 | string trigger = tmpMsg.GetHL7ItemValue("MSH-9.2")[0]; 221 | string originatingApp = tmpMsg.GetHL7ItemValue("MSH-3")[0]; 222 | string originatingSite = tmpMsg.GetHL7ItemValue("MSH-4")[0]; 223 | string messageID = tmpMsg.GetHL7ItemValue("MSH-10")[0]; 224 | string processingID = tmpMsg.GetHL7ItemValue("MSH-11")[0]; 225 | string hl7Version = tmpMsg.GetHL7ItemValue("MSH-12")[0]; 226 | string ackTimestamp = DateTime.Now.Year.ToString() + DateTime.Now.Month.ToString() + DateTime.Now.Day.ToString() + DateTime.Now.Hour.ToString() + DateTime.Now.Minute.ToString(); 227 | 228 | StringBuilder ACKString = new StringBuilder(); 229 | ACKString.Append((char)0x0B); 230 | ACKString.Append("MSH|^~\\&|HL7Listener|HL7Listener|" + originatingSite + "|" + originatingApp + "|" + ackTimestamp + "||ACK^" + trigger + "|" + messageID + "|" + processingID + "|" + hl7Version); 231 | ACKString.Append((char)0x0D); 232 | ACKString.Append("MSA|CA|" + messageID); 233 | ACKString.Append((char)0x1C); 234 | ACKString.Append((char)0x0D); 235 | return ACKString.ToString(); 236 | } 237 | 238 | public bool IsRunning() 239 | { 240 | return this.runThread; 241 | } 242 | 243 | /// 244 | /// Set and get the values of the SendACK option. This can be used to override sending of ACK messages. 245 | /// 246 | public bool SendACK 247 | { 248 | get { return this.sendACK; } 249 | set { this.sendACK = value; } 250 | } 251 | 252 | 253 | /// 254 | /// The PassthruHost property identifies the host to pass the messages through to 255 | /// 256 | public string PassthruHost 257 | { 258 | set { this.passthruHost = value; } 259 | get { return this.passthruHost; } 260 | } 261 | 262 | 263 | /// 264 | /// The PassthruPort property identifies the remote port to pass the messages thought to. 265 | /// 266 | public int PassthruPort 267 | { 268 | set { this.passthruPort = value; } 269 | get { return this.passthruPort; } 270 | } 271 | 272 | 273 | /// 274 | /// The FilePath property contains the path to archive the received messages to 275 | /// 276 | public string FilePath 277 | { 278 | set { this.archivePath = value; } 279 | get { return this.archivePath; } 280 | } 281 | 282 | /// 283 | /// The Encoding property contains the text encoding used for messages received 284 | /// 285 | public string Encoding 286 | { 287 | set { this.encoding = value; } 288 | get { return this.encoding; } 289 | } 290 | 291 | /// 292 | /// Write debug events to the console. 293 | /// 294 | /// 295 | private void LogDebug(string message) 296 | { 297 | debugQueue.Enqueue(message); 298 | } 299 | 300 | 301 | /// 302 | /// Write a warning events to the console 303 | /// 304 | /// 305 | private void LogWarning(string message) 306 | { 307 | warningQueue.Enqueue(message); 308 | } 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /src/RemoveHL7Item.cs: -------------------------------------------------------------------------------- 1 | /* Filename: RemoveHL7Item.cs 2 | * 3 | * Author: Rob Holme (rob@holme.com.au) 4 | * 5 | * Credits: Code to handle the Path and LiteralPath parameter sets, and expansion of wildcards is based 6 | * on Oisin Grehan's post: http://www.nivot.org/blog/post/2008/11/19/Quickstart1ACmdletThatProcessesFilesAndDirectories 7 | * 8 | * Date: 30/08/2016 9 | * 10 | * Notes: Implements the cmdlet to update the value of a specific item from a HL7 v2 message. 11 | * 12 | */ 13 | 14 | namespace HL7Tools 15 | { 16 | using System; 17 | using System.Collections.Generic; 18 | using System.IO; 19 | using System.Text; 20 | using System.Linq; 21 | using System.Management.Automation; 22 | 23 | // CmdLet: Remove-HL7Item 24 | // Removes the value of a specific item from the message 25 | [Cmdlet(VerbsCommon.Remove, "HL7Item", SupportsShouldProcess = true)] 26 | public class RemoveHL7Item : PSCmdlet 27 | { 28 | private string[] itemPosition; 29 | private string[] paths; 30 | private bool expandWildcards = false; 31 | private string[] filter = new string[] { }; 32 | private bool filterConditionsMet = true; 33 | private bool allrepeats; 34 | private string encoding = "UTF-8"; 35 | 36 | // Parameter set for the -Path and -LiteralPath parameters. A parameter set ensures these options are mutually exclusive. 37 | // A LiteralPath is used in situations where the filename actually contains wild card characters (eg File[1-10].txt) and you want 38 | // to use the literaral file name instead of treating it as a wildcard search. 39 | [Parameter( 40 | Mandatory = true, 41 | ValueFromPipeline = false, 42 | ValueFromPipelineByPropertyName = true, 43 | ParameterSetName = "Literal") 44 | ] 45 | [Alias("PSPath", "Name", "Filename")] 46 | [ValidateNotNullOrEmpty] 47 | public string[] LiteralPath 48 | { 49 | get { return this.paths; } 50 | set { this.paths = value; } 51 | } 52 | 53 | [Parameter( 54 | Position = 0, 55 | Mandatory = true, 56 | ValueFromPipeline = true, 57 | ValueFromPipelineByPropertyName = true, 58 | ParameterSetName = "Path") 59 | 60 | ] 61 | [ValidateNotNullOrEmpty] 62 | 63 | public string[] Path 64 | { 65 | get { return this.paths; } 66 | set 67 | { 68 | this.expandWildcards = true; 69 | this.paths = value; 70 | } 71 | } 72 | 73 | // A parameter for position of the item to remove 74 | [Parameter( 75 | Mandatory = true, 76 | Position = 1, 77 | HelpMessage = "Position of the item to return, e.g. PID-3.1" 78 | )] 79 | [Alias("Item")] 80 | public string[] ItemPosition 81 | { 82 | get { return this.itemPosition; } 83 | set { this.itemPosition = value; } 84 | } 85 | 86 | 87 | // Parameter to optionally filter the messages based on matching message contents 88 | [Parameter( 89 | Mandatory = false, 90 | Position = 2, 91 | HelpMessage = "Filter on message contents")] 92 | public string[] Filter 93 | { 94 | get { return this.filter; } 95 | set { this.filter = value; } 96 | } 97 | 98 | // Do not wait for ACKs responses if this switch is set 99 | [Parameter( 100 | Mandatory = false, 101 | HelpMessage = "Update all repeats of an item" 102 | )] 103 | public SwitchParameter RemoveAllRepeats 104 | { 105 | get { return this.allrepeats; } 106 | set { this.allrepeats = value; } 107 | } 108 | 109 | // Parameter to specify the message character encoding format 110 | [Parameter( 111 | Mandatory = false, 112 | Position = 3, 113 | HelpMessage = "Text encoding ('UTF-8' | 'ISO-8859-1'")] 114 | [ValidateSet("UTF-8", "ISO-8859-1")] 115 | public string Encoding 116 | { 117 | get { return this.encoding; } 118 | set { this.encoding = value; } 119 | } 120 | 121 | /// 122 | /// get the HL7 item provided via the cmdlet parameter HL7ItemPosition 123 | /// 124 | protected override void ProcessRecord() 125 | { 126 | 127 | // validate the that all of the list of locations to remove are valid. 128 | foreach (string item in this.itemPosition) { 129 | // confirm each filter is formatted correctly 130 | if (!Common.IsItemLocationValid(item)) { 131 | ArgumentException ex = new ArgumentException(item + " does not appear to be a valid HL7 location. Ensure the -ItemPosition list is formatted correctly."); 132 | ErrorRecord error = new ErrorRecord(ex, "InvalidFilter", ErrorCategory.InvalidArgument, item); 133 | this.WriteError(error); 134 | return; 135 | } 136 | } 137 | 138 | // confirm the filter parameter is valid before processing any files 139 | foreach (string currentFilter in this.filter) { 140 | // confirm each filter is formatted correctly 141 | if (!Common.IsFilterValid(currentFilter)) { 142 | ArgumentException ex = new ArgumentException(currentFilter + " does not appear to be a valid filter"); 143 | ErrorRecord error = new ErrorRecord(ex, "InvalidFilter", ErrorCategory.InvalidArgument, currentFilter); 144 | this.WriteError(error); 145 | return; 146 | } 147 | } 148 | 149 | // set the text encoding 150 | Encoding encoder = System.Text.Encoding.GetEncoding(this.encoding); 151 | WriteVerbose("Encoding: " + encoder.EncodingName); 152 | 153 | // expand the file or directory information provided in the -Path or -LiteralPath parameters 154 | foreach (string path in paths) { 155 | // This will hold information about the provider containing the items that this path string might resolve to. 156 | ProviderInfo provider; 157 | 158 | // This will be used by the method that processes literal paths 159 | PSDriveInfo drive; 160 | 161 | // this contains the paths to process for this iteration of the loop to resolve and optionally expand wildcards. 162 | List filePaths = new List(); 163 | 164 | // if the path provided is a directory, expand the files in the directy and add these to the list. 165 | if (Directory.Exists(path)) { 166 | filePaths.AddRange(Directory.GetFiles(path)); 167 | } 168 | 169 | // not a directory, could be a wildcard or literal filepath 170 | else { 171 | // expand wildcards. This assumes if the user listed a directory it is literal 172 | if (expandWildcards) { 173 | // Turn *.txt into foo.txt,foo2.txt etc. If path is just "foo.txt," it will return unchanged. If the filepath expands into a directory ignore it. 174 | foreach (string expandedFilePath in this.GetResolvedProviderPathFromPSPath(path, out provider)) { 175 | if (!Directory.Exists(expandedFilePath)) { 176 | filePaths.Add(expandedFilePath); 177 | } 178 | } 179 | } 180 | else { 181 | // no wildcards, so don't try to expand any * or ? symbols. 182 | filePaths.Add(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(path, out provider, out drive)); 183 | } 184 | // ensure that this path (or set of paths after wildcard expansion) 185 | // is on the filesystem. A wildcard can never expand to span multiple providers. 186 | if (Common.IsFileSystemPath(provider, path) == false) { 187 | // no, so skip to next path in paths. 188 | continue; 189 | } 190 | } 191 | 192 | // At this point, we have a list of paths on the filesystem, process each file. 193 | foreach (string filePath in filePaths) { 194 | // If the file does not exist display an error and return. 195 | if (!File.Exists(filePath)) { 196 | FileNotFoundException fileException = new FileNotFoundException("File not found", filePath); 197 | ErrorRecord fileNotFoundError = new ErrorRecord(fileException, "FileNotFound", ErrorCategory.ObjectNotFound, filePath); 198 | WriteError(fileNotFoundError); 199 | return; 200 | } 201 | 202 | // process the message 203 | try { 204 | // assume the filter is true, until a failed match is found 205 | this.filterConditionsMet = true; 206 | // load the file into a HL7Message object for processing 207 | string fileContents = File.ReadAllText(filePath, encoder); 208 | HL7Message message = new HL7Message(fileContents); 209 | // if a filter was supplied, evaluate if the file matches the filter condition 210 | if (this.filter != null) { 211 | // check to see is all of the filter conditions are met (ie AND all filters supplied). 212 | foreach (string currentFilter in this.filter) { 213 | bool anyItemMatch = false; 214 | string filterItem = Common.GetFilterItem(currentFilter); 215 | string filterValue = Common.GetFilterValue(currentFilter); 216 | // for repeating fields, only one of the items returned has to match for the filter to be evaluated as true. 217 | foreach (string itemValue in message.GetHL7ItemValue(filterItem)) { 218 | if (itemValue.ToUpper() == filterValue.ToUpper()) { 219 | anyItemMatch = true; 220 | } 221 | } 222 | // if none of the repeating field items match, then fail the filter match for this file. 223 | if (!anyItemMatch) { 224 | this.filterConditionsMet = false; 225 | } 226 | } 227 | } 228 | 229 | // if the filter supplied matches this message (or no filter provided) then process the file to optain the HL7 item requested 230 | if (filterConditionsMet) { 231 | foreach (string locationItem in this.itemPosition) { 232 | List hl7Items = message.GetHL7Item(locationItem); 233 | // if the hl7Items array is empty, the item was not found in the message 234 | if (hl7Items.Count == 0) { 235 | WriteWarning("Item " + this.itemPosition + " not found in the message " + filePath); 236 | } 237 | 238 | // items were located in the message, so proceed with replacing the original value with the new value. 239 | else { 240 | // update all repeats/occurances of the specified item 241 | if (this.allrepeats) { 242 | foreach (HL7Item item in hl7Items) { 243 | RemoveHL7ItemResult result = new RemoveHL7ItemResult(item.ToString(), filePath, locationItem); 244 | item.SetValueFromString(string.Empty); 245 | WriteObject(result); 246 | } 247 | } 248 | // update only the first occurrance. This is the default action. 249 | else { 250 | RemoveHL7ItemResult result = new RemoveHL7ItemResult(hl7Items.ElementAt(0).ToString(), filePath, locationItem); 251 | hl7Items.ElementAt(0).SetValueFromString(string.Empty); 252 | WriteObject(result); 253 | } 254 | // Write changes to the file. Replace the segment delimeter with the system newline string as this is being written to a file. 255 | string cr = ((char)0x0D).ToString(); 256 | string newline = System.Environment.NewLine; 257 | if (this.ShouldProcess(filePath, "Saving changes to file")) { 258 | System.IO.File.WriteAllText(filePath, message.ToString().Replace(cr, newline), encoder); 259 | } 260 | } 261 | } 262 | } 263 | } 264 | 265 | // if the file does not start with a MSH segment, the constructor will throw an exception. 266 | catch (System.ArgumentException) { 267 | ArgumentException argException = new ArgumentException("The file does not appear to be a valid HL7 v2 message", filePath); 268 | ErrorRecord invalidFileError = new ErrorRecord(argException, "FileNotValid", ErrorCategory.InvalidData, filePath); 269 | WriteError(invalidFileError); 270 | return; 271 | } 272 | } 273 | } 274 | } 275 | } 276 | 277 | /// 278 | /// An object containing the results to be returned to the pipeline. 279 | /// 280 | public class RemoveHL7ItemResult 281 | { 282 | private string oldValue; 283 | private string location; 284 | private string filename; 285 | 286 | /// 287 | /// The previous value of the HL7 item that was removed 288 | /// 289 | public string DeletedValue 290 | { 291 | get { return this.oldValue; } 292 | set { this.oldValue = value; } 293 | } 294 | 295 | /// 296 | /// The filename containing the item returned 297 | /// 298 | public string Filename 299 | { 300 | get { return this.filename; } 301 | set { this.filename = value; } 302 | } 303 | 304 | /// 305 | /// The location of the HL7 item that was changed. e.g. PID-3.1 306 | /// 307 | public string HL7Item 308 | { 309 | get { return this.location.ToUpper(); } 310 | set { this.location = value; } 311 | } 312 | 313 | /// 314 | /// 315 | /// 316 | /// 317 | /// 318 | public RemoveHL7ItemResult(string OldValue, string Filename, string HL7Item) 319 | { 320 | this.oldValue = OldValue; 321 | this.filename = Filename; 322 | this.location = HL7Item; 323 | } 324 | } 325 | 326 | } 327 | -------------------------------------------------------------------------------- /src/SetHL7Item.cs: -------------------------------------------------------------------------------- 1 | /* Filename: UpdateHL7Item.cs 2 | * 3 | * Author: Rob Holme (rob@holme.com.au) 4 | * 5 | * Credits: Code to handle the Path and LiteralPath parameter sets, and expansion of wildcards is based 6 | * on Oisin Grehan's post: http://www.nivot.org/blog/post/2008/11/19/Quickstart1ACmdletThatProcessesFilesAndDirectories 7 | * 8 | * Date: 29/08/2016 9 | * 10 | * Notes: Implements the cmdlet to update the value of a specific item from a HL7 v2 message. 11 | * 12 | */ 13 | 14 | namespace HL7Tools 15 | { 16 | using System; 17 | using System.Collections.Generic; 18 | using System.IO; 19 | using System.Text; 20 | using System.Linq; 21 | using System.Management.Automation; 22 | 23 | // CmdLet: Update-HL7Item 24 | // Replaces the value of a specific item from the message 25 | [Cmdlet(VerbsCommon.Set, "HL7Item", SupportsShouldProcess = true)] 26 | public class SetHL7Item : PSCmdlet 27 | { 28 | private string itemPosition; 29 | private string[] paths; 30 | private bool expandWildcards = false; 31 | private string[] filter = new string[] { }; 32 | private bool filterConditionsMet = true; 33 | private string newValue; 34 | private bool allrepeats = false; 35 | private bool appendValue = false; 36 | private string encoding = "UTF-8"; 37 | 38 | // Parameter set for the -Path and -LiteralPath parameters. A parameter set ensures these options are mutually exclusive. 39 | // A LiteralPath is used in situations where the filename actually contains wild card characters (eg File[1-10].txt) and you want 40 | // to use the literaral file name instead of treating it as a wildcard search. 41 | [Parameter( 42 | Mandatory = true, 43 | ValueFromPipeline = true, 44 | ValueFromPipelineByPropertyName = true, 45 | ParameterSetName = "Literal") 46 | ] 47 | [Alias("PSPath", "Name", "Filename", "Fullname")] 48 | [ValidateNotNullOrEmpty] 49 | public string[] LiteralPath 50 | { 51 | get { return this.paths; } 52 | set { this.paths = value; } 53 | } 54 | 55 | [Parameter( 56 | Position = 0, 57 | Mandatory = true, 58 | ValueFromPipeline = true, 59 | ValueFromPipelineByPropertyName = true, 60 | ParameterSetName = "Path") 61 | 62 | ] 63 | [ValidateNotNullOrEmpty] 64 | public string[] Path 65 | { 66 | get { return this.paths; } 67 | set 68 | { 69 | this.expandWildcards = true; 70 | this.paths = value; 71 | } 72 | } 73 | 74 | // A parameter for position of the item to return 75 | [Parameter( 76 | Mandatory = true, 77 | Position = 1, 78 | HelpMessage = "Position of the item to return, e.g. PID-3.1" 79 | )] 80 | [Alias("Item")] 81 | public string ItemPosition 82 | { 83 | get { return this.itemPosition; } 84 | set { this.itemPosition = value; } 85 | } 86 | 87 | // Parameter to supply the value of the item to be changed to 88 | [Parameter( 89 | Mandatory = true, 90 | Position = 2, 91 | HelpMessage = "New value")] 92 | public string Value 93 | { 94 | get { return this.newValue; } 95 | set { this.newValue = value; } 96 | } 97 | 98 | // Parameter to optionally filter the messages based on matching message contents 99 | [Parameter( 100 | Mandatory = false, 101 | Position = 3, 102 | HelpMessage = "Filter on message contents")] 103 | public string[] Filter 104 | { 105 | get { return this.filter; } 106 | set { this.filter = value; } 107 | } 108 | 109 | // Parameter to specify the message character encoding format 110 | [Parameter( 111 | Mandatory = false, 112 | Position = 4, 113 | HelpMessage = "Text encoding ('UTF-8' | 'ISO-8859-1'")] 114 | [ValidateSet("UTF-8", "ISO-8859-1")] 115 | public string Encoding 116 | { 117 | get { return this.encoding; } 118 | set { this.encoding = value; } 119 | } 120 | 121 | // Update all repeating values for the specified element 122 | [Parameter( 123 | Mandatory = false, 124 | HelpMessage = "Update all repeats of an item" 125 | )] 126 | public SwitchParameter UpdateAllRepeats 127 | { 128 | get { return this.allrepeats; } 129 | set { this.allrepeats = value; } 130 | } 131 | 132 | // Append the user supplied value to the existing item value 133 | [Parameter( 134 | Mandatory = false, 135 | HelpMessage = "Append the value supplied to the existing value" 136 | )] 137 | [Alias("Append")] 138 | public SwitchParameter AppendToExistingValue 139 | { 140 | get { return this.appendValue; } 141 | set { this.appendValue = value; } 142 | } 143 | 144 | 145 | /// 146 | /// get the HL7 item provided via the cmdlet parameter HL7ItemPosition 147 | /// 148 | protected override void ProcessRecord() 149 | { 150 | // confirm the item location parameter is valid before processing any files 151 | if (!Common.IsHL7LocationStringValid(this.itemPosition)) { 152 | ArgumentException ex = new ArgumentException(this.itemPosition + " does not appear to be a valid HL7 item"); 153 | ErrorRecord error = new ErrorRecord(ex, "InvalidElement", ErrorCategory.InvalidArgument, this.itemPosition); 154 | this.WriteError(error); 155 | return; 156 | } 157 | 158 | // confirm the filter parameter is valid before processing any files 159 | foreach (string currentFilter in this.filter) { 160 | // confirm each filter is formatted correctly 161 | if (!Common.IsFilterValid(currentFilter)) { 162 | ArgumentException ex = new ArgumentException(currentFilter + " does not appear to be a valid filter"); 163 | ErrorRecord error = new ErrorRecord(ex, "InvalidFilter", ErrorCategory.InvalidArgument, currentFilter); 164 | this.WriteError(error); 165 | return; 166 | } 167 | } 168 | 169 | // set the text encoding 170 | Encoding encoder = System.Text.Encoding.GetEncoding(this.encoding); 171 | WriteVerbose("Encoding: " + encoder.EncodingName); 172 | 173 | // expand the file or directory information provided in the -Path or -LiteralPath parameters 174 | foreach (string path in paths) { 175 | // This will hold information about the provider containing the items that this path string might resolve to. 176 | ProviderInfo provider; 177 | 178 | // This will be used by the method that processes literal paths 179 | PSDriveInfo drive; 180 | 181 | // this contains the paths to process for this iteration of the loop to resolve and optionally expand wildcards. 182 | List filePaths = new List(); 183 | 184 | // if the path provided is a directory, expand the files in the directy and add these to the list. 185 | if (Directory.Exists(path)) { 186 | filePaths.AddRange(Directory.GetFiles(path)); 187 | } 188 | 189 | // not a directory, could be a wildcard or literal filepath 190 | else { 191 | // expand wildcards. This assumes if the user listed a directory it is literal 192 | if (expandWildcards) { 193 | // Turn *.txt into foo.txt,foo2.txt etc. If path is just "foo.txt," it will return unchanged. If the filepath expands into a directory ignore it. 194 | foreach (string expandedFilePath in this.GetResolvedProviderPathFromPSPath(path, out provider)) { 195 | if (!Directory.Exists(expandedFilePath)) { 196 | filePaths.Add(expandedFilePath); 197 | } 198 | } 199 | } 200 | else { 201 | // no wildcards, so don't try to expand any * or ? symbols. 202 | filePaths.Add(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(path, out provider, out drive)); 203 | } 204 | // ensure that this path (or set of paths after wildcard expansion) 205 | // is on the filesystem. A wildcard can never expand to span multiple providers. 206 | if (Common.IsFileSystemPath(provider, path) == false) { 207 | // no, so skip to next path in paths. 208 | continue; 209 | } 210 | } 211 | 212 | // At this point, we have a list of paths on the filesystem, process each file. 213 | foreach (string filePath in filePaths) { 214 | // If the file does not exist display an error and return. 215 | if (!File.Exists(filePath)) { 216 | FileNotFoundException fileException = new FileNotFoundException("File not found", filePath); 217 | ErrorRecord fileNotFoundError = new ErrorRecord(fileException, "FileNotFound", ErrorCategory.ObjectNotFound, filePath); 218 | WriteError(fileNotFoundError); 219 | return; 220 | } 221 | 222 | // if the ItemPosition parameter is not in the correct format display an error and return 223 | if (!Common.IsItemLocationValid(this.itemPosition)) { 224 | ArgumentException argException = new ArgumentException("The -ItemPosition parameter does not appear to be in the correct format.", this.itemPosition); 225 | ErrorRecord parameterError = new ErrorRecord(argException, "ParameterNotValid", ErrorCategory.InvalidArgument, this.itemPosition); 226 | WriteError(parameterError); 227 | return; 228 | } 229 | 230 | // process the message 231 | try { 232 | // assume the filter is true, until a failed match is found 233 | this.filterConditionsMet = true; 234 | // load the file into a HL7Message object for processing 235 | string fileContents = File.ReadAllText(filePath, encoder); 236 | HL7Message message = new HL7Message(fileContents); 237 | // if a filter was supplied, evaluate if the file matches the filter condition 238 | if (this.filter != null) { 239 | // check to see is all of the filter conditions are met (ie AND all filters supplied). 240 | foreach (string currentFilter in this.filter) { 241 | bool anyItemMatch = false; 242 | string filterItem = Common.GetFilterItem(currentFilter); 243 | string filterValue = Common.GetFilterValue(currentFilter); 244 | // for repeating fields, only one of the items returned has to match for the filter to be evaluated as true. 245 | foreach (string itemValue in message.GetHL7ItemValue(filterItem)) { 246 | if (itemValue.ToUpper() == filterValue.ToUpper()) { 247 | anyItemMatch = true; 248 | } 249 | } 250 | // if none of the repeating field items match, then fail the filter match for this file. 251 | if (!anyItemMatch) { 252 | this.filterConditionsMet = false; 253 | } 254 | } 255 | } 256 | 257 | // if the filter supplied matches this message (or no filter provided) then process the file to optain the HL7 item requested 258 | if (filterConditionsMet) { 259 | List hl7Items = message.GetHL7Item(itemPosition); 260 | // if the hl7Items array is empty, the item was not found in the message 261 | if (hl7Items.Count == 0) { 262 | WriteWarning("Item " + this.itemPosition + " not found in the message " + filePath); 263 | } 264 | 265 | // items were located in the message, so proceed with replacing the original value with the new value. 266 | else { 267 | // update all repeats/occurances of the specified item 268 | if (this.allrepeats) { 269 | foreach (HL7Item item in hl7Items) { 270 | // appeand the new value to the existing value of the item if -AppendToExistingValue switch is set 271 | if (appendValue) { 272 | this.newValue = item.ToString() + this.newValue; 273 | } 274 | // update the item value 275 | SetHL7ItemResult result = new SetHL7ItemResult(this.newValue, item.ToString(), filePath, this.itemPosition); 276 | item.SetValueFromString(this.newValue); 277 | WriteObject(result); 278 | } 279 | } 280 | // update only the first occurrance. This is the default action. 281 | else { 282 | // append the new value to the existing value of the item if -AppendToExistingValue switch is set 283 | if (appendValue) { 284 | this.newValue = hl7Items.ElementAt(0).ToString() + this.newValue; 285 | } 286 | // update the item value 287 | SetHL7ItemResult result = new SetHL7ItemResult(this.newValue, hl7Items.ElementAt(0).ToString(), filePath, this.itemPosition); 288 | hl7Items.ElementAt(0).SetValueFromString(this.newValue); 289 | WriteObject(result); 290 | } 291 | // Write changes to the file. Replace the segment delimeter with the system newline string as this is being written to a file. 292 | string cr = ((char)0x0D).ToString(); 293 | string newline = System.Environment.NewLine; 294 | if (this.ShouldProcess(filePath, "Saving changes to file")) { 295 | System.IO.File.WriteAllText(filePath, message.ToString().Replace(cr, newline), encoder); 296 | } 297 | } 298 | } 299 | } 300 | 301 | // if the file does not start with a MSH segment, the constructor will throw an exception. 302 | catch (System.ArgumentException) { 303 | ArgumentException argException = new ArgumentException("The file does not appear to be a valid HL7 v2 message", filePath); 304 | ErrorRecord invalidFileError = new ErrorRecord(argException, "FileNotValid", ErrorCategory.InvalidData, filePath); 305 | WriteError(invalidFileError); 306 | return; 307 | } 308 | } 309 | } 310 | } 311 | } 312 | 313 | /// 314 | /// An object containing the results to be returned to the pipeline. 315 | /// 316 | public class SetHL7ItemResult 317 | { 318 | private string newValue; 319 | private string oldValue; 320 | private string location; 321 | private string filename; 322 | 323 | /// 324 | /// The new value of the HL7 item 325 | /// 326 | public string NewValue 327 | { 328 | get { return this.newValue; } 329 | set { this.newValue = value; } 330 | } 331 | 332 | /// 333 | /// The previous value of the HL7 item 334 | /// 335 | public string OldValue 336 | { 337 | get { return this.oldValue; } 338 | set { this.oldValue = value; } 339 | } 340 | 341 | /// 342 | /// The filename containing the item returned 343 | /// 344 | public string Filename 345 | { 346 | get { return this.filename; } 347 | set { this.filename = value; } 348 | } 349 | 350 | /// 351 | /// The location of the HL7 item that was changed. e.g. PID-3.1 352 | /// 353 | public string HL7Item 354 | { 355 | get { return this.location.ToUpper(); } 356 | set { this.location = value; } 357 | } 358 | 359 | /// 360 | /// 361 | /// 362 | /// 363 | /// 364 | public SetHL7ItemResult(string NewValue, string OldValue, string Filename, string HL7Item) 365 | { 366 | this.newValue = NewValue; 367 | this.oldValue = OldValue; 368 | this.filename = Filename; 369 | this.location = HL7Item; 370 | } 371 | } 372 | 373 | } 374 | -------------------------------------------------------------------------------- /src/SendHL7Message.cs: -------------------------------------------------------------------------------- 1 | /* Filename: SendHL7Message.cs 2 | * 3 | * Author: Rob Holme (rob@holme.com.au) 4 | * 5 | * Credits: Code to handle the Path and LiteralPath parameter sets, and expansion of wildcards is based 6 | * on Oisin Grehan's post: http://www.nivot.org/blog/post/2008/11/19/Quickstart1ACmdletThatProcessesFilesAndDirectories 7 | * 8 | * Date: 21/07/2016 9 | * 10 | * Notes: Implements a cmdlet to send a HL7 v2 message via TCP (framed using MLLP). 11 | * 12 | */ 13 | 14 | namespace HL7Tools 15 | { 16 | using System; 17 | using System.IO; 18 | using System.Text; 19 | using System.Collections.Generic; 20 | using System.Management.Automation; 21 | using System.Net.Sockets; 22 | using System.Net.Security; 23 | using System.Security.Authentication; 24 | using System.Security.Cryptography.X509Certificates; 25 | 26 | [Cmdlet("Send", "HL7Message")] 27 | public class SendHL7Message : PSCmdlet 28 | { 29 | private string hostname; 30 | private int port; 31 | private int delayBetweenMessages; 32 | private bool noACK; 33 | private string[] paths; 34 | private bool expandWildcards = false; 35 | private string[] messageString; 36 | private string encoding = "UTF-8"; 37 | private bool useTls; 38 | private bool skipCertificateCheck = false; 39 | 40 | 41 | // Parameter set for the -LiteralPath parameter. This is the path to the file to send. 42 | [Parameter( 43 | Position = 0, 44 | Mandatory = true, 45 | ValueFromPipeline = false, 46 | ValueFromPipelineByPropertyName = true, 47 | ParameterSetName = "Literal") 48 | ] 49 | [Alias("PSPath", "Name", "Filename")] 50 | [ValidateNotNullOrEmpty] 51 | public string[] LiteralPath 52 | { 53 | get { return this.paths; } 54 | set { this.paths = value; } 55 | } 56 | 57 | // Parameter set for the -Path parameter. This is the path to the file to send. Supports wilcard expansion. 58 | [Parameter( 59 | Position = 0, 60 | Mandatory = true, 61 | ParameterSetName = "Path") 62 | 63 | ] 64 | [ValidateNotNullOrEmpty] 65 | public string[] Path 66 | { 67 | get { return paths; } 68 | set 69 | { 70 | this.expandWildcards = true; 71 | this.paths = value; 72 | } 73 | } 74 | 75 | // Parameter set for the message string to send. 76 | [Parameter( 77 | Position = 0, 78 | Mandatory = true, 79 | ParameterSetName = "MessageString") 80 | 81 | ] 82 | [ValidateNotNullOrEmpty] 83 | public string[] MessageString 84 | { 85 | get { return this.messageString; } 86 | set { this.messageString = value; } 87 | } 88 | 89 | // The remote IP address or Hostname to send the HL7 message to 90 | [Alias("ComputerName", "Server", "IPAddress")] 91 | [Parameter( 92 | Mandatory = true, 93 | Position = 1, 94 | HelpMessage = "Remote Hostname or IP Address" 95 | )] 96 | public string HostName 97 | { 98 | get { return this.hostname; } 99 | set { this.hostname = value; } 100 | } 101 | 102 | // The port number of the remote listener to send the message to 103 | [Parameter( 104 | Mandatory = true, 105 | Position = 2, 106 | HelpMessage = "Remote listener port number" 107 | )] 108 | [ValidateRange(1, 65535)] 109 | public int Port 110 | { 111 | get { return this.port; } 112 | set { this.port = value; } 113 | } 114 | 115 | // Do not wait for ACKs responses if this switch is set 116 | [Parameter( 117 | Mandatory = false, 118 | HelpMessage = "Do not wait for ACK response" 119 | )] 120 | public SwitchParameter NoACK 121 | { 122 | get { return this.noACK; } 123 | set { this.noACK = value; } 124 | } 125 | 126 | // wait between sending messages 127 | [Parameter( 128 | Mandatory = false, 129 | Position = 3, 130 | HelpMessage = "Delay between sending messages (seconds)" 131 | )] 132 | [ValidateRange(0, 600)] 133 | public int Delay 134 | { 135 | get { return this.delayBetweenMessages; } 136 | set { this.delayBetweenMessages = value; } 137 | } 138 | 139 | // The encoding used when sending the message 140 | [Parameter( 141 | Mandatory = false, 142 | Position = 4, 143 | HelpMessage = "Message text encoding" 144 | )] 145 | [ValidateSet("UTF-8", "ISO-8859-1")] 146 | public string Encoding 147 | { 148 | get { return this.encoding; } 149 | set { this.encoding = value; } 150 | } 151 | 152 | // secure the connection to the server using TLS 153 | [Parameter( 154 | Mandatory = false, 155 | HelpMessage = "Use TLS to secure the connection (if supported by server)" 156 | )] 157 | public SwitchParameter UseTLS 158 | { 159 | get { return this.useTls; } 160 | set { this.useTls = value; } 161 | } 162 | 163 | // ignore TLS certificate errors, connect regardless of trust or validity errors. 164 | [Parameter( 165 | Mandatory = false, 166 | HelpMessage = "Ignore TLS certificate errors" 167 | )] 168 | public SwitchParameter SkipCertificateCheck 169 | { 170 | get { return this.skipCertificateCheck; } 171 | set { this.skipCertificateCheck = value; } 172 | } 173 | 174 | 175 | 176 | 177 | /// 178 | /// Send each of the files provided 179 | /// 180 | protected override void ProcessRecord() 181 | { 182 | // if the -MessageString parameter is supplied just send the message and return 183 | if (MessageString != null) { 184 | // convert to string with lines delimited by 185 | string message = ""; 186 | foreach (string line in MessageString) { 187 | message += line + (char)0x0D; 188 | } 189 | SendMessageHelper(message); 190 | return; 191 | } 192 | 193 | // otherwise assume -Path or -LiteralPath parameter used to identify the file to send. Read in the matching file(s) content. 194 | foreach (string path in paths) 195 | { 196 | // This will hold information about the provider containing the items that this path string might resolve to. 197 | ProviderInfo provider; 198 | 199 | // This will be used by the method that processes literal paths 200 | PSDriveInfo drive; 201 | 202 | // this contains the paths to process for this iteration of the loop to resolve and optionally expand wildcards. 203 | List filePaths = new List(); 204 | 205 | // if the path provided is a directory, expand the files in the directory and add these to the list. 206 | if (Directory.Exists(path)) 207 | { 208 | filePaths.AddRange(Directory.GetFiles(path)); 209 | } 210 | 211 | // not a directory, could be a wild-card or literal filepath 212 | else 213 | { 214 | // expand wild-cards. This assumes if the user listed a directory it is literal 215 | if (expandWildcards) 216 | { 217 | // Turn *.txt into foo.txt,foo2.txt etc. If path is just "foo.txt," it will return unchanged. If the filepath expands into a directory ignore it. 218 | foreach (string expandedFilePath in this.GetResolvedProviderPathFromPSPath(path, out provider)) 219 | { 220 | if (!Directory.Exists(expandedFilePath)) 221 | { 222 | filePaths.Add(expandedFilePath); 223 | } 224 | } 225 | } 226 | else 227 | { 228 | // no wildcards, so don't try to expand any * or ? symbols. 229 | filePaths.Add(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(path, out provider, out drive)); 230 | } 231 | // ensure that this path (or set of paths after wildcard expansion) 232 | // is on the filesystem. A wildcard can never expand to span multiple providers. 233 | if (Common.IsFileSystemPath(provider, path) == false) 234 | { 235 | // no, so skip to next path in paths. 236 | continue; 237 | } 238 | } 239 | 240 | // At this point, we have a list of paths on the filesystem, send each file to the remote endpoint 241 | foreach (string filePath in filePaths) 242 | { 243 | // confirm the file exists 244 | if (!File.Exists(filePath)) 245 | { 246 | FileNotFoundException fileException = new FileNotFoundException("File not found", filePath); 247 | ErrorRecord fileNotFoundError = new ErrorRecord(fileException, "FileNotFound", ErrorCategory.ObjectNotFound, filePath); 248 | WriteError(fileNotFoundError); 249 | return; 250 | } 251 | 252 | // get the contents of the file 253 | try 254 | { 255 | string fileContents = File.ReadAllText(filePath); 256 | // Send the file 257 | SendMessageHelper(fileContents, filePath); 258 | } 259 | catch (Exception ex) 260 | { 261 | ArgumentException argException = new ArgumentException("Unable to open file.", filePath); 262 | ErrorRecord fileNotFoundError = new ErrorRecord(argException, "FileNotValid", ErrorCategory.InvalidData, filePath); 263 | WriteError(fileNotFoundError); 264 | WriteDebug($"Exception: {ex}"); 265 | return; 266 | } 267 | 268 | 269 | // delay between sending messages 270 | if (this.delayBetweenMessages > 0) 271 | { 272 | System.Threading.Thread.Sleep(this.delayBetweenMessages * 1000); 273 | } 274 | } 275 | } 276 | } 277 | 278 | /// 279 | /// Prepare the HL7 Message for sending 280 | /// 281 | private void SendMessageHelper(string MessageContent, string filePath = null) { 282 | // send the file to the endpoint using MLLP framing 283 | TcpClient tcpConnection = new TcpClient(); 284 | tcpConnection.SendTimeout = 10000; 285 | tcpConnection.ReceiveTimeout = 10000; 286 | try 287 | { 288 | System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch(); 289 | 290 | // save the string as a HL7Message, this will validate the file is a HL7 v2 message. 291 | HL7Message message = new HL7Message(MessageContent); 292 | WriteVerbose("Connecting to " + this.hostname + ":" + this.port); 293 | 294 | // create a TCP socket connection to the receiver, start timing the elapsed time to deliver the message and receive the ACK 295 | timer.Start(); 296 | tcpConnection.Connect(this.hostname, this.port); 297 | 298 | string[] ackLines = null; 299 | // connect using TLS if -UseTLS switch supplied, otherwise use plain text 300 | if (this.useTls) { 301 | ackLines = SendMessageTLS(tcpConnection, message, this.skipCertificateCheck); 302 | } 303 | else { 304 | ackLines = SendMessage(tcpConnection, message); 305 | } 306 | 307 | // stop timing the operation, output the result object 308 | timer.Stop(); 309 | SendHL7MessageResult result = new SendHL7MessageResult("Successful", ackLines, DateTime.Now, message.ToString().Split((char)0x0D), this.hostname, this.port, filePath, timer.Elapsed.TotalMilliseconds / 1000); 310 | WriteObject(result); 311 | WriteVerbose("Closing TCP session\n"); 312 | } 313 | // if the file does not start with a MSH segment, the constructor will throw an exception. 314 | catch (ArgumentException ae) 315 | { 316 | if (filePath != null) { 317 | ArgumentException argException = new ArgumentException("The file not appear to be a valid HL7 v2 message", filePath); 318 | ErrorRecord fileNotFoundError = new ErrorRecord(argException, "FileNotValid", ErrorCategory.InvalidData, filePath); 319 | WriteError(fileNotFoundError); 320 | } 321 | else { 322 | ArgumentException argException = new ArgumentException("The -MessageString value does not appear to be a valid HL7 v2 message", ""); 323 | ErrorRecord fileNotFoundError = new ErrorRecord(argException, "FileNotValid", ErrorCategory.InvalidData, ""); 324 | WriteError(fileNotFoundError); 325 | } 326 | WriteDebug($"Exception: {ae}"); 327 | return; 328 | } 329 | // catch failed TCP connections 330 | catch (SocketException se) 331 | { 332 | ErrorRecord SocketError = new ErrorRecord(se, "ConnectionError", ErrorCategory.ConnectionError, this.hostname + ":" + this.port); 333 | WriteError(SocketError); 334 | return; 335 | } 336 | finally 337 | { 338 | tcpConnection.Close(); 339 | } 340 | } 341 | 342 | /// 343 | /// Send the message via MLLP using a TLS secured connection 344 | /// 345 | private string[] SendMessageTLS(TcpClient Connection, HL7Message Message, bool SkipCertCheck) 346 | { 347 | // set the text encoding 348 | Encoding encoder = System.Text.Encoding.GetEncoding(this.encoding); 349 | WriteVerbose("Encoding: " + encoder.EncodingName); 350 | 351 | // get the ssl stream. Use hostname as SNI name. Ignore cert errors if -SkipCertificateCheck is set 352 | WriteVerbose("Using TLS"); 353 | SslStream sslStream; 354 | if (!SkipCertCheck) 355 | { 356 | WriteVerbose("Enforcing certificate validation"); 357 | sslStream = new SslStream(Connection.GetStream()); 358 | } 359 | else 360 | { 361 | WriteVerbose("Ignoring certificate validation errors"); 362 | sslStream = new SslStream(Connection.GetStream(), false, new RemoteCertificateValidationCallback(SkipServerCertificateValidation), null); 363 | } 364 | sslStream.AuthenticateAsClient(this.hostname); 365 | 366 | // get the message text with MLLP framing 367 | Byte[] writeBuffer = new Byte[4096]; 368 | writeBuffer = encoder.GetBytes(Message.GetMLLPFramedMessage()); 369 | sslStream.Write(writeBuffer, 0, writeBuffer.Length); 370 | sslStream.Flush(); 371 | WriteVerbose("Message sent"); 372 | 373 | // wait for ack unless the -NoACK switch was set 374 | string[] ackLines = null; 375 | if (!this.noACK) 376 | { 377 | WriteVerbose("Waiting for ACK ..."); 378 | Byte[] readBuffer = new Byte[4096]; 379 | int bytesRead = sslStream.Read(readBuffer, 0, 4096); 380 | string ackMessage = encoder.GetString(readBuffer, 0, bytesRead); 381 | ackLines = StripMLLPFrame(ackMessage); 382 | } 383 | sslStream.Close(); 384 | return ackLines; 385 | } 386 | 387 | 388 | /// 389 | /// Send the message via MLLP 390 | /// 391 | private string[] SendMessage(TcpClient Connection, HL7Message Message) 392 | { 393 | // set the text encoding 394 | Encoding encoder = System.Text.Encoding.GetEncoding(this.encoding); 395 | WriteVerbose("Encoding: " + encoder.EncodingName); 396 | 397 | NetworkStream tcpStream = Connection.GetStream(); 398 | 399 | // get the message text with MLLP framing 400 | Byte[] writeBuffer = new Byte[4096]; 401 | writeBuffer = encoder.GetBytes(Message.GetMLLPFramedMessage()); 402 | tcpStream.Write(writeBuffer, 0, writeBuffer.Length); 403 | tcpStream.Flush(); 404 | WriteVerbose("Message sent"); 405 | 406 | // wait for ack unless the -NoACK switch was set 407 | string[] ackLines = null; 408 | if (!this.noACK) 409 | { 410 | WriteVerbose("Waiting for ACK ..."); 411 | Byte[] readBuffer = new Byte[4096]; 412 | int bytesRead = tcpStream.Read(readBuffer, 0, 4096); 413 | string ackMessage = encoder.GetString(readBuffer, 0, bytesRead); 414 | ackLines = StripMLLPFrame(ackMessage); 415 | } 416 | tcpStream.Close(); 417 | return ackLines; 418 | } 419 | 420 | 421 | /// 422 | /// Strip the MLLP framing from the message string 423 | /// 424 | private string[] StripMLLPFrame(string MLLPFramedMessage) 425 | { 426 | string[] messageLines = null; 427 | // look for the start of the MLLP frame (VT control character) 428 | int start = MLLPFramedMessage.IndexOf((char)0x0B); 429 | if (start >= 0) 430 | { 431 | // Search for the end of the MLLP frame (FS control character) 432 | int end = MLLPFramedMessage.IndexOf((char)0x1C); 433 | if (end > start) 434 | { 435 | // split the ACK message on character (segment delimiter), output each segment of the ACK on a new line 436 | // remove the last character if present, otherwise the final element in the array will be empty when splitting the string 437 | string ackString = MLLPFramedMessage.Substring(start + 1, end - 1); 438 | if (ackString[ackString.Length - 1] == (char)0x0D) 439 | { 440 | ackString = ackString.Substring(0, ackString.Length - 1); 441 | } 442 | messageLines = ackString.Split((char)0x0D); 443 | } 444 | } 445 | return messageLines; 446 | } 447 | 448 | /// 449 | /// The following method is invoked by the RemoteCertificateValidationDelegate. 450 | /// Always return true, to ignore 451 | /// 452 | public static bool SkipServerCertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) 453 | { 454 | return true; 455 | } 456 | 457 | } 458 | 459 | /// 460 | /// An object containing the results to be returned to the pipeline 461 | /// 462 | public class SendHL7MessageResult 463 | { 464 | private string status; 465 | private string[] ackMessage; 466 | private DateTime timeSent; 467 | private string[] messageSent; 468 | private string filename; 469 | private string remoteHost; 470 | private int port; 471 | private double elapsedSeconds; 472 | 473 | /// 474 | /// The value of the HL7 item 475 | /// 476 | public string Status 477 | { 478 | get { return this.status; } 479 | set { this.status = value; } 480 | } 481 | 482 | /// 483 | /// The timestamp of when the message was sent 484 | /// 485 | public DateTime TimeSent 486 | { 487 | get { return this.timeSent; } 488 | set { this.timeSent = value; } 489 | } 490 | 491 | /// 492 | /// The ACK response received from the remote host 493 | /// 494 | public string[] ACKMessage 495 | { 496 | get { return this.ackMessage; } 497 | set { this.ackMessage = value; } 498 | } 499 | 500 | /// 501 | /// A copy of the HL7 message sent 502 | /// 503 | public string[] MessageSent 504 | { 505 | get { return this.messageSent; } 506 | set { this.messageSent = value; } 507 | } 508 | 509 | /// 510 | /// The remote host the message was sent to 511 | /// 512 | public string RemoteHost 513 | { 514 | get { return this.remoteHost; } 515 | set { this.remoteHost = value; } 516 | } 517 | 518 | /// 519 | /// The TCP port of the remote server 520 | /// 521 | public int Port 522 | { 523 | get { return this.port; } 524 | set { this.port = value; } 525 | } 526 | 527 | /// 528 | /// The filename containing the message sent 529 | /// 530 | public string Filename 531 | { 532 | get { return this.filename; } 533 | set { this.filename = value; } 534 | } 535 | 536 | /// 537 | /// The time elapsed in seconds to send the message 538 | /// 539 | public double ElapsedSeconds 540 | { 541 | get { return this.elapsedSeconds; } 542 | set { this.elapsedSeconds = value; } 543 | } 544 | 545 | /// 546 | /// 547 | /// 548 | public SendHL7MessageResult(string Status, string[] Ack = null, DateTime? SendTime = null, string[] HL7Message = null, string RemoteHost = null, int? Port = null, string Filename = null, double? ElapsedSeconds = null) 549 | { 550 | // null-coalescing operator. Uses the SentTime value, unless it is null in which case DateTime.Now is assigned as the value. 551 | this.timeSent = SendTime ?? DateTime.Now; 552 | this.status = Status; 553 | this.ackMessage = Ack; 554 | this.messageSent = HL7Message; 555 | this.remoteHost = RemoteHost; 556 | this.port = Port ?? 0; 557 | this.filename = Filename; 558 | this.elapsedSeconds = ElapsedSeconds ?? 0; 559 | } 560 | } 561 | 562 | } 563 | --------------------------------------------------------------------------------