├── 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 |
--------------------------------------------------------------------------------