├── .gitignore
├── .vscode
├── launch.json
└── settings.json
├── CHANGELOG.md
├── CI
├── Assert.ps1
├── CI.ps1
├── InstallPowerShell.ps1
└── Publish.ps1
├── Examples
└── Join-Object.Examples.ps1
├── Install.ps1
├── Join-Object.ps1
├── Join-Object.psd1
├── Join-Object.psm1
├── LICENSE
├── README.md
├── Tests
├── CompareData
│ ├── DataSetSmall, DataTable - PSCustomObject. AllInBoth. DataTable.xml
│ ├── DataSetSmall, DataTable - PSCustomObject. AllInBoth.xml
│ ├── DataSetSmall, DataTable - PSCustomObject. DataTable. (DataTableTypes).xml
│ ├── DataSetSmall, DataTable - PSCustomObject. DataTable. (Error, Mismatch).xml
│ ├── DataSetSmall, DataTable - PSCustomObject. DataTable.xml
│ ├── DataSetSmall, DataTable - PSCustomObject. OnlyIfInBoth. DataTable.xml
│ ├── DataSetSmall, DataTable - PSCustomObject. OnlyIfInBoth.xml
│ ├── DataSetSmall, DataTable - PSCustomObject. PassThru. (DataTableTypes).xml
│ ├── DataSetSmall, DataTable - PSCustomObject. PassThru.xml
│ ├── DataSetSmall, DataTable - PSCustomObject.xml
│ ├── DataSetSmall, DataTable - PSCustomObjectJunk. DataTable. (AllowColumnsMerging).xml
│ ├── DataSetSmall, DataTable - PSCustomObjectJunk. PassThru. (AllowColumnsMerging).xml
│ ├── DataSetSmall, DataTable - PSCustomObjectKeyArray. (Comparer).xml
│ ├── DataSetSmall, DataTable - PSCustomObjectMulti. (SubArray).xml
│ ├── DataSetSmall, DataTable - [DataTable]PSCustomObjectJunk. PassThru. (AllowColumnsMerging).xml
│ ├── DataSetSmall, DataTableMulti - PSCustomObjectMulti. AllInBoth. (SubGroups).xml
│ ├── DataSetSmall, DataTableMulti - PSCustomObjectMulti. AllInBoth. DataTable. (DuplicateLines).xml
│ ├── DataSetSmall, DataTableMulti - PSCustomObjectMulti. AllInBoth. DataTable. (SubGroups).xml
│ ├── DataSetSmall, DataTableMulti - PSCustomObjectMulti. AllInLeft. PassThru. (SubGroups).xml
│ ├── DataSetSmall, DataTableMulti - [DataTable]PSCustomObjectMulti. AllInLeft. PassThru. (SubGroups).xml
│ ├── DataSetSmall, PSCustomObject - DataTable. (DBNull to $null).xml
│ ├── DataSetSmall, PSCustomObject - DataTable. (Error, Mismatch).xml
│ ├── DataSetSmall, PSCustomObject - DataTable. (JoinScript String).xml
│ ├── DataSetSmall, PSCustomObject - DataTable. (JoinScript).xml
│ ├── DataSetSmall, PSCustomObject - DataTable. (KeepRightJoinProperty).xml
│ ├── DataSetSmall, PSCustomObject - DataTable. (Multi Join).xml
│ ├── DataSetSmall, PSCustomObject - DataTable. (Ordered).xml
│ ├── DataSetSmall, PSCustomObject - DataTable. AllInBoth. DataTable.xml
│ ├── DataSetSmall, PSCustomObject - DataTable. AllInBoth.xml
│ ├── DataSetSmall, PSCustomObject - DataTable. DataTable.xml
│ ├── DataSetSmall, PSCustomObject - DataTable. OnlyIfInBoth. DataTable.xml
│ ├── DataSetSmall, PSCustomObject - DataTable. OnlyIfInBoth.xml
│ ├── DataSetSmall, PSCustomObject - DataTable. PassThru.xml
│ ├── DataSetSmall, PSCustomObject - DataTable.xml
│ ├── DataSetSmall, PSCustomObjectMulti - DataTable. (SubArray).xml
│ ├── DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInBoth. (SubGroups Key).xml
│ ├── DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInBoth. (SubGroups).xml
│ ├── DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInBoth. DataTable. (DuplicateLines).xml
│ ├── DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInBoth. DataTable. (SubGroups Key).xml
│ ├── DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInBoth. DataTable. (SubGroups).xml
│ ├── DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInLeft. PassThru. (SubGroups).xml
│ ├── DataSetSmall, PSCustomObjectMulti - [PSCustomObject]DataTableMulti. AllInLeft. PassThru. (SubGroups).xml
│ ├── DataSetSmall, PSCustomObject[0] - DataTable. (Single).xml
│ └── DataSetSmall, [Collections.ArrayList]PSCustomObject - DataTable.xml
├── Join-Object.Tests.Big.ps1
├── Join-Object.Tests.ps1
├── TestDataSetBig100K.db
├── TestDataSetBig10k.db
└── TestHelpers.ps1
├── appveyor.yml
├── azure-pipelines.yml
└── morelinq.3.2.0
├── .signature.p7s
├── COPYING.txt
├── lib
├── net451
│ ├── MoreLinq.dll
│ ├── MoreLinq.pdb
│ └── MoreLinq.xml
├── netstandard1.0
│ ├── MoreLinq.dll
│ ├── MoreLinq.pdb
│ └── MoreLinq.xml
└── netstandard2.0
│ ├── MoreLinq.dll
│ ├── MoreLinq.pdb
│ └── MoreLinq.xml
└── morelinq.3.2.0.nupkg
/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/.gitignore
--------------------------------------------------------------------------------
/.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 | "type": "PowerShell",
9 | "request": "launch",
10 | "name": "PowerShell Pester Tests",
11 | "script": "Import-Module -Name '.\\' -Force ; Invoke-Pester", // Change to '.\\ModuleName.psd1' if Git name different
12 | "args": [""],
13 | "cwd": "${workspaceFolder}"
14 | },
15 | {
16 | "type": "PowerShell",
17 | "request": "launch",
18 | "name": "PowerShell Pester Tests Big",
19 | "script": "Import-Module -Name '.\\' -Force ; Invoke-Pester -Script .\\Tests\\Join-Object.Tests.Big.ps1",
20 | "args": [
21 | ""
22 | ],
23 | "cwd": "${workspaceFolder}"
24 | },
25 | {
26 | "type": "PowerShell",
27 | "request": "launch",
28 | "name": "PowerShell Launch Current File",
29 | "script": "${file}",
30 | "args": [],
31 | "cwd": "${file}"
32 | },
33 | {
34 | "type": "PowerShell",
35 | "request": "launch",
36 | "name": "PowerShell Launch Current File in Temporary Console",
37 | "script": "${file}",
38 | "args": [],
39 | "cwd": "${file}",
40 | "createTemporaryIntegratedConsole": true
41 | },
42 | {
43 | "type": "PowerShell",
44 | "request": "launch",
45 | "name": "PowerShell Launch Current File w/Args Prompt",
46 | "script": "${file}",
47 | "args": [
48 | "${command:SpecifyScriptArgs}"
49 | ],
50 | "cwd": "${file}"
51 | },
52 | {
53 | "type": "PowerShell",
54 | "request": "attach",
55 | "name": "PowerShell Attach to Host Process",
56 | "processId": "${command:PickPSHostProcess}",
57 | "runspaceId": 1
58 | },
59 | {
60 | "type": "PowerShell",
61 | "request": "launch",
62 | "name": "PowerShell Interactive Session",
63 | "cwd": ""
64 | }
65 | ]
66 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "CSVs",
4 | "Clixml",
5 | "Cmdlet",
6 | "Computername",
7 | "Deserialize",
8 | "Deserialized",
9 | "HKLM",
10 | "Hashtable",
11 | "Inno",
12 | "LINQ",
13 | "Nuget",
14 | "PSSQ",
15 | "Recurse",
16 | "SSNs",
17 | "Scriptblock",
18 | "Serializer",
19 | "Silveira's",
20 | "Sqlite",
21 | "VARCHAR",
22 | "Veyor",
23 | "adddays",
24 | "birthdate",
25 | "endregion",
26 | "gmail",
27 | "isnot",
28 | "jsmith",
29 | "morelinq",
30 | "msiexec",
31 | "netstandard",
32 | "notin",
33 | "notlike",
34 | "notmatch",
35 | "prepend",
36 | "setvariable",
37 | "unencrypted"
38 | ]
39 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 2.0.3 - 2022/04/02 (Stable)
2 | ### Fixed
3 | * [Issues #16](https://github.com/ili101/Join-Object/issues/16) - Fixed escape error bug when not using `-DataTable`, and `-AddKey` parameter contains a spaces (thank you [franklesniak](https://github.com/franklesniak) for reporting).
4 | * Fixed escape error bug when using `-DataTable`, and `-AddKey` parameter contains single quotes.
5 |
6 | ## 2.0.2 - 2020/11/09 (Stable)
7 | ### New
8 | * Add support for [ArrayList] input in addition for [Array] And [DataTable].
9 |
10 | ## 2.0.1 - 2019/08/24 (Stable)
11 | ### Documentation
12 | * Updated Description and Tags.
13 |
14 | ## 2.0.0 - 2019/08/22 (Stable)
15 | ### New
16 | * PowerShell Core 7 is now supported!
17 | * `-LeftJoinScript` and `-RightJoinScript` now support non string output (String is still used if scriptblock provided and not Func\`2).
18 | * `-Comparer` allow use of custom [EqualityComparer].
19 | ### Fixed
20 | * Fixed DuplicateLines with non DataTable output can create output with "hidden" sub arrays. Linq Join seems to not unroll the output so for example you can get this output:
21 | ```
22 | IDD Name Junk IntT R_Sub R_IntO
23 | --- ---- ---- ---- ----- ------
24 | 1 A AAA 5 S1 6
25 | 1 A AAA 5 S12 62
26 | 3 C S3
27 | 4 D
28 | ```
29 | It looks like an array with 4 lines of PSCustomObjects but it's actually an array with 3 lines as the first line is actually an array containing the first 2 lines. This will be unrolled in the new version to an array with 4 PSCustomObjects. :warning: breaking change.
30 | * PowerShell Core 7: Join-Object SubGroups [EmptyPartition`1] Fix.
31 | * `-PassThru` will now throw an error when used with `-Type OnlyIfInBoth` as lines cannot be removed, and with `-Type AllInLeft` + `-RightMultiMode DuplicateLines` as lines cannot be duplicated. :warning: breaking change.
32 | ### Changed
33 | `-AddKey` is now a String that takes the name of the key column to create. :warning: breaking change.
34 | ### Improved
35 | * `/Examples/Join-Object.Examples.ps1` was updated.
36 | ### Updated
37 | * MoreLinq Updated to 3.2.0.
38 | ### Testing
39 | * Update To Module.Template 2.0.1, Test now use Azure pipeline Windows 2019 and AppVeyor on Ubuntu1804 and Windows 2019 (PowerShell Framework and core 7.0.0 Preview 3).
40 | * Update to custom version of Assert 0.9.5.
41 | * PowerShell Core 7: Test 1 ExpectedErrorMessage Fix.
42 | * PowerShell Core 7: Tests PSCustomObject.Count -eq 1 Fix.
43 | ### Code Cleanup
44 | * Change format to VSCode default on all files.
45 | * cSpell.words updated and spelling corrections.
46 | * Refactor Tests.
47 | * Refactor Join-Object.
48 |
49 | ## 1.0.1 - 2018/12/17 (Stable)
50 | ### Added
51 | * **-AllowColumnsMerging** Allow duplicate columns in the Left and Right Objects, will overwrite the conflicting Left data with the Right data (Ignoring Nulls), Supported only on DataTable output for now.
52 |
53 | ## 1.0.0 - 2018/11/20 (Stable)
54 | ### Improved
55 | * Major rewrite of the code, The main Scriptblock is now dynamically constructed.
56 | * Error handling with $PSCmdlet.ThrowTerminatingError.
57 | ### Fixed
58 | * Undo "Fix JoinFunction scope to support JoinProperty" (Unnecessary feature).
59 | ### Added
60 | * **-LeftMultiMode** and **-RightMultiMode** with options ('SingleOnly', 'DuplicateLines', 'SubGroups').
61 | * **-AddKey** can be used with "-Type AllInBoth" to add a column named "Key" containing the joining key.
62 | ### Removed
63 | * **-MultiLeft** replaced by MultiMode.
64 | * **-RightAsGroup** replaced by MultiMode.
65 |
66 | :warning: if you used the GitHub (Beta branch) version there are breaking changes.
67 |
68 | ## 0.1.8 - 2018/11/07 (Beta branch)
69 | ### Updated
70 | * MoreLinq updated to 3.0.0.
71 | ### Fixed
72 | * Fix Multi JoinProperty comparing column name.
73 | * Fix JoinFunction scope to support JoinProperty.
74 | * Minor bug fixes and improvements.
75 |
76 | ## 0.1.7 - 2018/03/13 (Beta branch)
77 | ### Added
78 | * **-Type AllInBoth** option.
79 | * **-DataTableTypes [Hashtable]** allow Overwrite of DataTable columns types.
80 | * **-RightAsGroup [String]** Join the right side as sub table in column with the selected name.
81 | * **-MultiLeft** allow multiple rows on the left side.
82 | * **-KeepRightJoinProperty** don't remove the right join property.
83 | ### Fixed
84 | * Remove unused parameter option **-Type AllInRight**
85 |
86 | ## 0.1.6 - 2018/03/13 (Beta)
87 | ### Fixed
88 | * Error "Cannot set Column 'foo' to be null. Please use DBNull instead." when using -DataTable and -AllInLeft and some Left lines don't have Right lines to join to.
89 |
90 | ## 0.1.5 - 2018/03/11 (Beta)
91 | ### Fixed
92 | * Error "Cannot set Column 'foo' to be null. Please use DBNull instead." when using -DataTable on DataTable data with nulls in it.
93 |
94 | ## 0.1.4 - 2018/03/08 (Beta)
95 | ### Added
96 | * **-DataTable** parameter to output as "DataTable".
97 | ### Fixed
98 | * Output when using PassThru on "-Left DataTable" was returning "Array" with "DataRow"s instead of "DataTable" with "DataRow"s Object.
99 |
100 | ## 0.1.2 - 2018/03/05 (Beta)
101 | ### Added
102 | * **-Type** parameter that supports "AllInLeft" and "OnlyIfInBoth".
103 | * **-RightJoinScript** and **-LeftJoinScript** parameters to support custom joining scripts.
104 | * **-RightJoinProperty** and **-LeftJoinProperty** now supports multiple Properties (String Array) to join on multiple columns.
105 |
106 | ## 0.1.1 - 2017-09-19 (Beta)
107 | ### Added
108 | * Convert DBNull to $null when going from DataTable to PSCustomObject.
109 |
110 | ## 0.1.0 - 2017-09-19 (Beta)
111 | * Join-Object initial release.
112 |
113 | ## 0.0.0 - 2017-07-28 (Alpha)
114 | * Join-Object LINQ Edition concept code.
--------------------------------------------------------------------------------
/CI/Assert.ps1:
--------------------------------------------------------------------------------
1 | # Fix Could not create SSL/TLS secure channel
2 | #$SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol
3 | #[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
4 | $Branch = 'Test2' #'master'
5 | $Repository = 'Assert'
6 | $PathZip = Join-Path ([IO.Path]::GetTempPath()) "$Repository-$Branch.zip"
7 | $PathPsd1 = Join-Path ([IO.Path]::GetTempPath()) "$Repository-$Branch" | Join-Path -ChildPath "$Repository.psd1"
8 |
9 | if (!(Test-Path $PathPsd1)) {
10 | Invoke-WebRequest "https://github.com/ili101/$Repository/archive/$Branch.zip" -OutFile $PathZip
11 | #[Net.ServicePointManager]::SecurityProtocol = $SecurityProtocol
12 | Expand-Archive -Path $PathZip -DestinationPath ([IO.Path]::GetTempPath())
13 | }
14 | Import-Module -Name $PathPsd1
--------------------------------------------------------------------------------
/CI/CI.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Handel Continuous Integration Testing in AppVeyor and Azure DevOps Pipelines.
4 | #>
5 | param
6 | (
7 | # AppVeyor Only - Update AppVeyor build name.
8 | [Switch]$Initialize,
9 | # Installs the module and invoke the Pester tests with the current version of PowerShell.
10 | [Switch]$Test,
11 | # AppVeyor Only - Upload results to AppVeyor "Tests" tab.
12 | [Switch]$Finalize,
13 | # AppVeyor and Azure - Upload module as AppVeyor Artifact.
14 | [Switch]$Artifact,
15 | # Azure - Runs PsScriptAnalyzer against one or more folders and pivots the results to form a report.
16 | [Switch]$Analyzer
17 | )
18 | $ErrorActionPreference = 'Stop'
19 | if ($Initialize) {
20 | $Psd1 = (Get-ChildItem -File -Filter *.psd1 -Name -Path (Split-Path $PSScriptRoot)).PSPath
21 | $ModuleVersion = (. ([Scriptblock]::Create((Get-Content -Path $Psd1 | Out-String)))).ModuleVersion
22 | Update-AppveyorBuild -Version "$ModuleVersion ($env:APPVEYOR_BUILD_NUMBER) $env:APPVEYOR_REPO_BRANCH"
23 | }
24 | if ($Test) {
25 | function Get-EnvironmentInfo {
26 | if ($null -eq $IsWindows -or $IsWindows) {
27 | # Get Windows Version
28 | try {
29 | $WinRelease, $WinVer = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" ReleaseId, CurrentMajorVersionNumber, CurrentMinorVersionNumber, CurrentBuildNumber, UBR
30 | $WindowsVersion = "$($WinVer -join '.') ($WinRelease)"
31 | }
32 | catch {
33 | $WindowsVersion = [System.Environment]::OSVersion.Version
34 | }
35 |
36 | # Get .Net Version
37 | # https://stackoverflow.com/questions/3487265/powershell-script-to-return-versions-of-net-framework-on-a-machine
38 | $Lookup = @{
39 | 378389 = [version]'4.5'
40 | 378675 = [version]'4.5.1'
41 | 378758 = [version]'4.5.1'
42 | 379893 = [version]'4.5.2'
43 | 393295 = [version]'4.6'
44 | 393297 = [version]'4.6'
45 | 394254 = [version]'4.6.1'
46 | 394271 = [version]'4.6.1'
47 | 394802 = [version]'4.6.2'
48 | 394806 = [version]'4.6.2'
49 | 460798 = [version]'4.7'
50 | 460805 = [version]'4.7'
51 | 461308 = [version]'4.7.1'
52 | 461310 = [version]'4.7.1'
53 | 461808 = [version]'4.7.2'
54 | 461814 = [version]'4.7.2'
55 | 528040 = [version]'4.8'
56 | 528049 = [version]'4.8'
57 | }
58 |
59 | # For One True framework (latest .NET 4x), change the Where-Object match
60 | # to PSChildName -eq "Full":
61 | $DotNetVersion = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse |
62 | Get-ItemProperty -name Version, Release -EA 0 |
63 | Where-Object { $_.PSChildName -eq "Full" } |
64 | Select-Object @{name = ".NET Framework"; expression = { $_.PSChildName } },
65 | @{name = "Product"; expression = { $Lookup[$_.Release] } },
66 | Version, Release
67 |
68 | # Output
69 | [PSCustomObject]($PSVersionTable + @{
70 | ComputerName = $env:Computername
71 | WindowsVersion = $WindowsVersion
72 | '.Net Version' = '{0} (Version: {1}, Release: {2})' -f $DotNetVersion.Product, $DotNetVersion.Version, $DotNetVersion.Release
73 | #EnvironmentPath = $env:Path
74 | })
75 | }
76 | else {
77 | # Output
78 | [PSCustomObject]($PSVersionTable + @{
79 | ComputerName = $env:Computername
80 | #EnvironmentPath = $env:Path
81 | })
82 | }
83 | }
84 |
85 | '[Info] Testing On:'
86 | Get-EnvironmentInfo
87 | '[Progress] Installing Module.'
88 | . .\Install.ps1
89 | '[Progress] Invoking Pester.'
90 | Invoke-Pester -OutputFile ('TestResultsPS{0}.xml' -f $PSVersionTable.PSVersion)
91 | }
92 | if ($Finalize) {
93 | '[Progress] Finalizing.'
94 | $Failure = $false
95 | $AppVeyorResultsUri = 'https://ci.appveyor.com/api/testresults/nunit/{0}' -f $env:APPVEYOR_JOB_ID
96 | foreach ($TestResultsFile in Get-ChildItem -Path 'TestResultsPS*.xml') {
97 | $TestResultsFilePath = $TestResultsFile.FullName
98 | "[Info] Uploading Files: $AppVeyorResultsUri, $TestResultsFilePath."
99 | # Add PowerShell version to test results
100 | $PSVersion = $TestResultsFile.Name.Replace('TestResults', '').Replace('.xml', '')
101 | [Xml]$Xml = Get-Content -Path $TestResultsFilePath
102 | Select-Xml -Xml $Xml -XPath '//test-case' | ForEach-Object { $_.Node.name = "$PSVersion " + $_.Node.name }
103 | $Xml.OuterXml | Out-File -FilePath $TestResultsFilePath
104 |
105 | #Invoke-RestMethod -Method Post -Uri $AppVeyorResultsUri -Body $Xml
106 | [Net.WebClient]::new().UploadFile($AppVeyorResultsUri, $TestResultsFilePath)
107 |
108 | if ($Xml.'test-results'.failures -ne '0') {
109 | $Failure = $true
110 | }
111 | }
112 | if ($Failure) {
113 | throw 'Tests failed.'
114 | }
115 | }
116 | if ($Artifact) {
117 | # Get Module Info
118 | $ModuleName = [System.IO.Path]::GetFileNameWithoutExtension((Get-ChildItem -File -Filter *.psm1 -Name -Path (Split-Path $PSScriptRoot)))
119 | $ModulePath = (Get-Module -Name $ModuleName -ListAvailable).ModuleBase | Split-Path
120 | $VersionLocal = ((Get-Module -Name $ModuleName -ListAvailable).Version | Measure-Object -Maximum).Maximum
121 | "[Progress] Artifact Start for Module: $ModuleName, Version: $VersionLocal."
122 | if ($env:APPVEYOR) {
123 | $ZipFileName = "{0} {1} {2} {3:yyyy-MM-dd HH-mm-ss}.zip" -f $ModuleName, $VersionLocal, $env:APPVEYOR_REPO_BRANCH, (Get-Date)
124 | $ZipFileFullPath = Join-Path -Path $PSScriptRoot -ChildPath $ZipFileName
125 | "[Info] Artifact. $ModuleName, ZipFileName: $ZipFileName."
126 | #Compress-Archive -Path $ModulePath -DestinationPath $ZipFileFullPath
127 | [System.IO.Compression.ZipFile]::CreateFromDirectory($ModulePath, $ZipFileFullPath, [System.IO.Compression.CompressionLevel]::Optimal, $true)
128 | Push-AppveyorArtifact $ZipFileFullPath -DeploymentName $ModuleName
129 | }
130 | elseif ($env:AGENT_NAME) {
131 | #Write-Host "##vso[task.setvariable variable=ModuleName]$ModuleName"
132 | Copy-Item -Path $ModulePath -Destination $env:Build_ArtifactStagingDirectory -Recurse
133 | }
134 | }
135 | if ($Analyzer) {
136 | if (!(Get-Module -Name PSScriptAnalyzer -ListAvailable)) {
137 | '[Progress] Installing PSScriptAnalyzer.'
138 | Install-Module -Name PSScriptAnalyzer -Force
139 | }
140 |
141 | if ($env:System_PullRequest_TargetBranch) {
142 | '[Progress] Get target branch.'
143 | $TempGitClone = Join-Path ([IO.Path]::GetTempPath()) (New-Guid)
144 | Copy-Item -Path $PWD -Destination $TempGitClone -Recurse
145 | (Get-Item (Join-Path $TempGitClone '.git')).Attributes += 'Hidden'
146 | "[Progress] git clean."
147 | git -C $TempGitClone clean -f
148 | "[Progress] git reset."
149 | git -C $TempGitClone reset --hard
150 | "[Progress] git checkout."
151 | git -C $TempGitClone checkout -q $env:System_PullRequest_TargetBranch
152 |
153 | $DirsToProcess = @{ 'Pull Request' = $PWD ; $env:System_PullRequest_TargetBranch = $TempGitClone }
154 | }
155 | else {
156 | $DirsToProcess = @{ 'GitHub' = $PWD }
157 | }
158 |
159 | "[Progress] Running Script Analyzer."
160 | $AnalyzerResults = $DirsToProcess.GetEnumerator() | ForEach-Object {
161 | $DirName = $_.Key
162 | Write-Verbose "[Progress] Running Script Analyzer on $DirName."
163 | Invoke-ScriptAnalyzer -Path $_.Value -Recurse -ErrorAction SilentlyContinue |
164 | Add-Member -MemberType NoteProperty -Name Location -Value $DirName -PassThru
165 | }
166 |
167 | if ($AnalyzerResults) {
168 | if (!(Get-Module -Name ImportExcel -ListAvailable)) {
169 | '[Progress] Installing ImportExcel.'
170 | Install-Module -Name ImportExcel -Force
171 | }
172 | '[Progress] Creating ScriptAnalyzer.xlsx.'
173 | $ExcelParams = @{
174 | Path = 'ScriptAnalyzer.xlsx'
175 | WorksheetName = 'FullResults'
176 | Now = $true
177 | Activate = $true
178 | Show = $false
179 | }
180 | $PivotParams = @{
181 | PivotTableName = 'BreakDown'
182 | PivotData = @{RuleName = 'Count' }
183 | PivotRows = 'Severity', 'RuleName'
184 | PivotColumns = 'Location'
185 | PivotTotals = 'Rows'
186 | }
187 | Remove-Item -Path $ExcelParams['Path'] -ErrorAction SilentlyContinue
188 |
189 | $PivotParams['PivotChartDefinition'] = New-ExcelChartDefinition -ChartType 'BarClustered' -Column (1 + $DirsToProcess.Count) -Title "Script analysis" -LegendBold
190 | $ExcelParams['PivotTableDefinition'] = New-PivotTableDefinition @PivotParams
191 |
192 | $AnalyzerResults | Export-Excel @ExcelParams
193 | '[Progress] Analyzer finished.'
194 | }
195 | else {
196 | "[Info] Invoke-ScriptAnalyzer didn't return any problems."
197 | }
198 | }
--------------------------------------------------------------------------------
/CI/InstallPowerShell.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Installs PowerShell Core on Windows.
4 | #>
5 | [CmdLetBinding()]
6 | Param
7 | (
8 | # Version to install in the format from the .msi, for example "7.0.0-preview.1"
9 | [Parameter(Mandatory)]
10 | [String]$Version
11 | )
12 | $ErrorActionPreference = 'Stop'
13 |
14 | '[Progress] Downloading PowerShell Core.'
15 | $MsiPath = Join-Path $env:TEMP "PowerShell-$Version-win-x64.msi"
16 | [System.Net.WebClient]::new().DownloadFile("https://github.com/PowerShell/PowerShell/releases/download/v$Version/PowerShell-$Version-win-x64.msi", $MsiPath)
17 |
18 | '[Progress] Installing PowerShell Core.'
19 | Start-Process 'msiexec.exe' -Wait -ArgumentList "/i $MsiPath /quiet"
20 | Remove-Item -Path $MsiPath
21 | $PowerShellFolder = $Version[0]
22 | if ($Version -like "*preview*") {
23 | $PowerShellFolder += '-preview'
24 | }
25 | $env:Path = "$env:ProgramFiles\PowerShell\$PowerShellFolder;$env:Path"
26 | '[Progress] PowerShell Core Installed.'
--------------------------------------------------------------------------------
/CI/Publish.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Deploy module to PowerShellGallery.
4 | #>
5 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "Success")]
6 | [CmdletBinding(DefaultParameterSetName = 'ModuleName')]
7 | Param
8 | (
9 | # The name of the installed module to be deployed, if not provided the name of the .psm1 file in the parent folder is used.
10 | [Parameter(ParameterSetName = 'ModuleName')]
11 | [ValidateNotNullOrEmpty()]
12 | [String]$ModuleName,
13 |
14 | # Publish module from path (module folder), if not provided -ModuleName is used.
15 | [Parameter(Mandatory, ParameterSetName = 'Path')]
16 | [ValidateNotNullOrEmpty()]
17 | [String]$Path,
18 |
19 | # Key for PowerShellGallery deployment, if not provided $env:NugetApiKey is used.
20 | [ValidateNotNullOrEmpty()]
21 | [String]$NugetApiKey,
22 |
23 | # Skip Version verification for PowerShellGallery deployment, can be used for first release.
24 | [Switch]$Force
25 | )
26 | $ErrorActionPreference = 'Stop'
27 |
28 | if ($Path) {
29 | $Path = Resolve-Path -Path $Path
30 | if ($Path.Count -ne 1) {
31 | throw ('Invalid Path, $Path.Count: {0}.' -f $Path.Count)
32 | }
33 | $Psd1Path = (Get-ChildItem -File -Filter *.psd1 -Path $Path -Recurse)[0].FullName
34 | $ModuleName = [System.IO.Path]::GetFileNameWithoutExtension($Psd1Path)
35 | $VersionLocal = (. ([Scriptblock]::Create((Get-Content -Path $Psd1Path | Out-String)))).ModuleVersion
36 | }
37 | else {
38 | # Get Script Root
39 | if ($PSScriptRoot) {
40 | $ScriptRoot = $PSScriptRoot
41 | }
42 | elseif ($psISE.CurrentFile.IsUntitled -eq $false) {
43 | $ScriptRoot = Split-Path -Path $psISE.CurrentFile.FullPath
44 | }
45 | elseif ($null -ne $psEditor.GetEditorContext().CurrentFile.Path -and $psEditor.GetEditorContext().CurrentFile.Path -notlike 'untitled:*') {
46 | $ScriptRoot = Split-Path -Path $psEditor.GetEditorContext().CurrentFile.Path
47 | }
48 | else {
49 | $ScriptRoot = '.'
50 | }
51 |
52 | # Get Module Info
53 | if (!$ModuleName) {
54 | $ModuleName = [System.IO.Path]::GetFileNameWithoutExtension((Get-ChildItem -File -Filter *.psm1 -Name -Path (Split-Path $ScriptRoot)))
55 | }
56 | $VersionLocal = ((Get-Module -Name $ModuleName -ListAvailable).Version | Measure-Object -Maximum).Maximum
57 | }
58 |
59 | "[Progress] Deploy Script Start for Module: $ModuleName, Version: $VersionLocal."
60 |
61 | # Deploy to PowerShell Gallery if run locally OR from AppVeyor & GitHub master
62 | if (!$env:APPVEYOR -or $env:APPVEYOR_REPO_BRANCH -eq 'master') {
63 | if ($env:APPVEYOR) {
64 | $Success = $true
65 | $AppVeyorProject = Invoke-RestMethod -Uri "https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG"
66 | $AppVeyorProject.build.jobs | ForEach-Object {
67 | '[Info] AppVeyor job name: "{0}", Id: {1}, Status: {2}.' -f $_.name, $_.jobId, $_.status
68 | if ($_.jobId -ne $env:APPVEYOR_JOB_ID -and $_.status -ne "success") {
69 | $Success = $false
70 | }
71 | }
72 | if (!$Success) {
73 | '[Info] There are filed jobs skipping PowerShell Gallery deploy.'
74 | break
75 | }
76 | }
77 | try {
78 | $VersionGallery = (Find-Module -Name $ModuleName -ErrorAction Stop).Version
79 | }
80 | catch {
81 | if ($_.Exception.Message -notlike 'No match was found for the specified search criteria*' -or !$Force) {
82 | throw $_
83 | }
84 | }
85 |
86 | "[Info] PowerShellGallery. $ModuleName, VersionGallery: $VersionGallery, VersionLocal: $VersionLocal."
87 | if ($VersionGallery -lt $VersionLocal -or $Force) {
88 | if (!$NugetApiKey) {
89 | $NugetApiKey = $env:NugetApiKey
90 | }
91 | "[Info] PowerShellGallery. Deploying $ModuleName version $VersionLocal."
92 | if ($Path) {
93 | Publish-Module -NuGetApiKey $NugetApiKey -Path $Path
94 | }
95 | else {
96 | Publish-Module -NuGetApiKey $NugetApiKey -Name $ModuleName -RequiredVersion $VersionLocal
97 | }
98 | }
99 | else {
100 | '[Info] PowerShellGallery Deploy Skipped (Version Check).'
101 | }
102 | }
103 | else {
104 | '[Info] PowerShellGallery Deploy Skipped.'
105 | }
106 | '[Progress] Deploy Ended.'
--------------------------------------------------------------------------------
/Examples/Join-Object.Examples.ps1:
--------------------------------------------------------------------------------
1 | $ExampleData = {
2 | # Left Object Example
3 | $PSCustomObject = @(
4 | [PSCustomObject]@{ ID = 1 ; Sub = 'S1' ; IntO = 6 }
5 | [PSCustomObject]@{ ID = 2 ; Sub = 'S2' ; IntO = 7 }
6 | [PSCustomObject]@{ ID = 3 ; Sub = 'S3' ; IntO = $null }
7 | )
8 | # Right Object Example (DataTable)
9 | $DataTable = [Data.DataTable]::new('Test')
10 | $null = $DataTable.Columns.Add('IDD', [System.Int32])
11 | $null = $DataTable.Columns.Add('Name')
12 | $null = $DataTable.Columns.Add('Junk')
13 | $null = $DataTable.Columns.Add('IntT', [System.Int32])
14 | $null = $DataTable.Rows.Add(1, 'foo', 'AAA', 123456)
15 | $null = $DataTable.Rows.Add(3, 'Bar', 'S3', $null)
16 | $null = $DataTable.Rows.Add(4, 'D', $null, $null)
17 | }
18 |
19 | . $ExampleData
20 | # Example 1: Join the 2 together ("Left Join" in this case).
21 | Join-Object -Left $PSCustomObject -Right $DataTable -LeftJoinProperty 'ID' -RightJoinProperty 'IDD' | Format-Table
22 | <# Output
23 | ID Sub IntO Name Junk IntT
24 | -- --- ---- ---- ---- ----
25 | 1 S1 6 foo AAA 123456
26 | 2 S2 7
27 | 3 S3 Bar S3
28 | #>
29 |
30 | . $ExampleData
31 | # Example 2A: Filtering columns.
32 | $Params = @{
33 | Left = $PSCustomObject
34 | Right = $DataTable
35 | LeftJoinProperty = 'ID'
36 | RightJoinProperty = 'IDD'
37 | ExcludeRightProperties = 'Junk' # Exclude column "Junk" from the right columns.
38 | Prefix = 'R_' # Add Prefix to the right columns.
39 | LeftProperties = 'ID', 'Sub' # Select columns to include from the right.
40 | }
41 | Join-Object @Params | Format-Table
42 | <# Output
43 | ID Sub R_Name R_IntT
44 | -- --- ------ ------
45 | 1 S1 foo 123456
46 | 2 S2
47 | 3 S3 Bar
48 | #>
49 |
50 | # Example 2B: Filtering renaming and reordering columns.
51 | $Params['LeftProperties'] = [ordered]@{ Sub = 'Subscription' ; ID = 'ID' } # Select columns to include from the right, rename and reorder them.
52 | Join-Object @Params | Format-Table
53 | <# Output
54 | Subscription ID R_Name R_IntT
55 | ------------ -- ------ ------
56 | S1 1 foo 123456
57 | S2 2
58 | S3 3 Bar
59 | #>
60 |
61 | . $ExampleData
62 | # Example 3: -Type. Options: AllInLeft (default), OnlyIfInBoth, AllInBoth.
63 | $Params = @{
64 | Left = $PSCustomObject
65 | Right = $DataTable
66 | LeftJoinProperty = 'ID'
67 | RightJoinProperty = 'IDD'
68 | Type = 'OnlyIfInBoth'
69 | }
70 | Join-Object @Params | Format-Table
71 | <# Output
72 | ID Sub IntO Name Junk IntT
73 | -- --- ---- ---- ---- ----
74 | 1 S1 6 foo AAA 123456
75 | 3 S3 Bar S3
76 | #>
77 |
78 | . $ExampleData
79 | # Example 4: Output format. (When input is [DataTable] containing [DBNull]s if output is [PSCustomObject] they will be converted to $null).
80 | $Params = @{
81 | Left = $PSCustomObject
82 | Right = $DataTable
83 | LeftJoinProperty = 'ID'
84 | RightJoinProperty = 'IDD'
85 | DataTable = $true # By default output format is PSCustomObject this changes it to DataTable.
86 | }
87 | Join-Object @Params | Format-Table
88 | <# This is a DataTable
89 | ID Sub IntO Name Junk IntT
90 | -- --- ---- ---- ---- ----
91 | 1 S1 6 foo AAA 123456
92 | 2 S2 7
93 | 3 S3 Bar S3
94 | #>
95 |
96 | . $ExampleData
97 | # Example 5: -PassThru. Editing the existing left object preserving it's existing type PSCustomObject/DataTable.
98 | $Params = @{
99 | Left = $PSCustomObject
100 | Right = $DataTable
101 | LeftJoinProperty = 'ID'
102 | RightJoinProperty = 'IDD'
103 | PassThru = $true
104 | }
105 | $null = Join-Object @Params
106 | $PSCustomObject | Format-Table
107 | <# $PSCustomObject changed to:
108 | ID Sub IntO Name Junk IntT
109 | -- --- ---- ---- ---- ----
110 | 1 S1 6 foo AAA 123456
111 | 2 S2 7
112 | 3 S3 Bar S3
113 | #>
114 |
115 | . $ExampleData
116 | # Example 6: JoinScript. Manipulate the JoinProperty for the comparison with a Scriptblock.
117 | $Params = @{
118 | Left = $PSCustomObject
119 | Right = $DataTable
120 | LeftJoinProperty = 'Sub'
121 | RightJoinProperty = 'IDD'
122 | LeftJoinScript = { param ($Line) $Line.Sub.Replace('S', '')} # For example change "Sub" column value from "S1" to "1" to compare to "IDD" column "1".
123 | }
124 | Join-Object @Params | Format-Table
125 | <# Output
126 | ID Sub IntO Name Junk IntT
127 | -- --- ---- ---- ---- ----
128 | 1 S1 6 foo AAA 123456
129 | 2 S2 7
130 | 3 S3 Bar S3
131 | #>
132 |
133 | . $ExampleData
134 | # Example 7: -AddKey. can be used with "-Type AllInBoth" to add a column containing the joining key.
135 | $Params = @{
136 | Left = $PSCustomObject
137 | Right = $DataTable
138 | LeftJoinProperty = 'ID'
139 | RightJoinProperty = 'IDD'
140 | LeftProperties = 'Sub'
141 | Type = 'AllInBoth'
142 | AddKey = 'Index'
143 | }
144 | Join-Object @Params | Format-Table
145 | <# Output
146 | Index Sub Name Junk IntT
147 | ----- --- ---- ---- ----
148 | 1 S1 foo AAA 123456
149 | 2 S2
150 | 3 S3 Bar S3
151 | 4 D
152 | #>
--------------------------------------------------------------------------------
/Install.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Installs module from Git clone or directly from GitHub.
4 | File must not have BOM for GitHub deploy to work.
5 | #>
6 | [CmdletBinding(DefaultParameterSetName = 'Default')]
7 | Param (
8 | # Path to install the module to, if not provided -Scope used.
9 | [Parameter(Mandatory, ParameterSetName = 'ModulePath')]
10 | [ValidateNotNullOrEmpty()]
11 | [String]$ModulePath,
12 |
13 | # Path to install the module to, PSModulePath "CurrentUser" or "AllUsers", if not provided "CurrentUser" used.
14 | [Parameter(Mandatory, ParameterSetName = 'Scope')]
15 | [ValidateSet('CurrentUser', 'AllUsers')]
16 | [string]
17 | $Scope = 'CurrentUser',
18 |
19 | # Get module from GitHub instead of local Git clone, for example "https://raw.githubusercontent.com/ili101/Module.Template/master/Install.ps1"
20 | [ValidateNotNullOrEmpty()]
21 | [Uri]$FromGitHub
22 | )
23 | # Set Files and Folders patterns to Include/Exclude.
24 | $IncludeFiles = @(
25 | '*.dll',
26 | '*.psd1',
27 | '*.psm1',
28 | '*.ps1',
29 | 'morelinq*'
30 | )
31 | $ExcludeFiles = @(
32 | 'Install.ps1'
33 | )
34 |
35 |
36 | function Invoke-MultiLike {
37 | [alias("LikeAny")]
38 | [CmdletBinding()]
39 | param
40 | (
41 | $InputObject,
42 | [Parameter(Mandatory)]
43 | [String[]]$Filters,
44 | [Switch]$Not
45 | )
46 | $FiltersRegex = foreach ($Filter In $Filters) {
47 | $Filter = [regex]::Escape($Filter)
48 | if ($Filter -match "^\\\*") {
49 | $Filter = $Filter.Remove(0, 2)
50 | }
51 | else {
52 | $Filter = '^' + $Filter
53 | }
54 | if ($Filter -match "\\\*$") {
55 | $Filter = $Filter.Substring(0, $Filter.Length - 2)
56 | }
57 | else {
58 | $Filter = $Filter + '$'
59 | }
60 | $Filter
61 | }
62 | if ($Not) {
63 | $InputObject -notmatch ($FiltersRegex -join '|').replace('\*', '.*').replace('\?', '.')
64 | }
65 | else {
66 | $InputObject -match ($FiltersRegex -join '|').replace('\*', '.*').replace('\?', '.')
67 | }
68 | }
69 |
70 | Try {
71 | Write-Verbose -Message 'Module installation started'
72 |
73 | if (!$ModulePath) {
74 | if ($Scope -eq 'CurrentUser') {
75 | $ModulePathIndex = 0
76 | }
77 | else {
78 | $ModulePathIndex = 1
79 | }
80 | if ($IsLinux -or $IsMacOS) {
81 | $ModulePathSeparator = ':'
82 | }
83 | else {
84 | $ModulePathSeparator = ';'
85 | }
86 | $ModulePath = ($env:PSModulePath -split $ModulePathSeparator)[$ModulePathIndex]
87 | }
88 |
89 | # Get $ModuleName, $TargetPath, [$Links]
90 | if ($FromGitHub) {
91 | # Fix Could not create SSL/TLS secure channel
92 | #$SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol
93 | #[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
94 |
95 | $WebClient = [System.Net.WebClient]::new()
96 | $GitUri = $FromGitHub.AbsolutePath.Split('/')[1, 2] -join '/'
97 | $GitBranch = $FromGitHub.AbsolutePath.Split('/')[3]
98 | $Links = (Invoke-RestMethod -Uri "https://api.github.com/repos/$GitUri/contents" -Body @{ref = $GitBranch }) | Where-Object { (LikeAny $_.name $IncludeFiles) -and (LikeAny $_.name $ExcludeFiles -Not) }
99 |
100 | $ModuleName = [System.IO.Path]::GetFileNameWithoutExtension(($Links | Where-Object { $_.name -like '*.psm1' }).name)
101 | $ModuleVersion = (. ([Scriptblock]::Create((Invoke-WebRequest -Uri ($Links | Where-Object { $_.name -eq "$ModuleName.psd1" }).download_url)))).ModuleVersion
102 | }
103 | else {
104 | $ModuleName = [System.IO.Path]::GetFileNameWithoutExtension((Get-ChildItem -File -Filter *.psm1 -Name -Path $PSScriptRoot))
105 | $ModuleVersion = (. ([Scriptblock]::Create((Get-Content -Path (Join-Path $PSScriptRoot "$ModuleName.psd1") | Out-String)))).ModuleVersion
106 | }
107 | $TargetPath = Join-Path -Path $ModulePath -ChildPath $ModuleName
108 | $TargetPath = Join-Path -Path $TargetPath -ChildPath $ModuleVersion
109 |
110 | # Create Directory
111 | if (-not (Test-Path -Path $TargetPath)) {
112 | $null = New-Item -Path $TargetPath -ItemType Directory -ErrorAction Stop
113 | Write-Verbose -Message ('Created module folder: "{0}"' -f $TargetPath)
114 | }
115 |
116 | # Copy Files
117 | if ($FromGitHub) {
118 | foreach ($Link in $Links) {
119 | $TargetPathItem = Join-Path -Path $TargetPath -ChildPath $Link.name
120 | if ($Link.type -ne 'dir') {
121 | $WebClient.DownloadFile($Link.download_url, $TargetPathItem)
122 | Write-Verbose -Message ('Installed module file: "{0}"' -f $Link.name)
123 | }
124 | else {
125 | if (-not (Test-Path -Path $TargetPathItem)) {
126 | $null = New-Item -Path $TargetPathItem -ItemType Directory -ErrorAction Stop
127 | Write-Verbose -Message ('Created module folder: "{0}"' -f $TargetPathItem)
128 | }
129 | $SubLinks = (Invoke-RestMethod -Uri $Link.git_url -Body @{recursive = '1' }).tree
130 | foreach ($SubLink in $SubLinks) {
131 | $TargetPathSub = Join-Path -Path $TargetPathItem -ChildPath $SubLink.path
132 | if ($SubLink.'type' -EQ 'tree') {
133 | if (-not (Test-Path -Path $TargetPathSub)) {
134 | $null = New-Item -Path $TargetPathSub -ItemType Directory -ErrorAction Stop
135 | Write-Verbose -Message ('Created module folder: "{0}"' -f $TargetPathSub)
136 | }
137 | }
138 | else {
139 | $WebClient.DownloadFile(
140 | ('https://raw.githubusercontent.com/{0}/{1}/{2}/{3}' -f $GitUri, $GitBranch, $Link.name, $SubLink.path),
141 | $TargetPathSub
142 | )
143 | }
144 | }
145 | }
146 | }
147 | }
148 | else {
149 | Get-ChildItem -Path $PSScriptRoot -Exclude $ExcludeFiles | Where-Object { LikeAny $_.Name $IncludeFiles } | ForEach-Object {
150 | if ($_.Attributes -ne 'Directory') {
151 | Copy-Item -Path $_ -Destination $TargetPath
152 | Write-Verbose -Message ('Installed module file "{0}"' -f $_)
153 | }
154 | else {
155 | Copy-Item -Path $_ -Destination $TargetPath -Recurse -Force
156 | Write-Verbose -Message ('Installed module folder "{0}"' -f $_)
157 | }
158 | }
159 | }
160 |
161 | # Import Module
162 | Write-Verbose -Message "$ModuleName module installation successful to $TargetPath"
163 | Import-Module -Name $ModuleName -Force
164 | Write-Verbose -Message "Module installed"
165 | }
166 | Catch {
167 | throw ('Failed installing module "{0}". Error: "{1}" in Line {2}' -f $ModuleName, $_, $_.InvocationInfo.ScriptLineNumber)
168 | }
169 | finally {
170 | #if ($FromGitHub) {
171 | # [Net.ServicePointManager]::SecurityProtocol = $SecurityProtocol
172 | #}
173 | Write-Verbose -Message 'Module installation end'
174 | }
--------------------------------------------------------------------------------
/Join-Object.ps1:
--------------------------------------------------------------------------------
1 | using namespace System.Data
2 | Add-Type -AssemblyName System.Data.DataSetExtensions
3 | function Join-Object {
4 | <#
5 | .SYNOPSIS
6 | Join data from two sets of objects based on a common value
7 |
8 | .DESCRIPTION
9 | Join data from two sets of objects based on a common value
10 |
11 | For more details, see the accompanying blog post:
12 | http://ramblingcookiemonster.github.io/Join-Object/
13 |
14 | For even more details, see the original code and discussions that this borrows from:
15 | Dave Wyatt's Join-Object - http://powershell.org/wp/forums/topic/merging-very-large-collections
16 | Lucio Silveira's Join-Object - http://blogs.msdn.com/b/powershell/archive/2012/07/13/join-object.aspx
17 |
18 | .PARAMETER Left
19 | 'Left' collection of objects to join. You can use the pipeline for Left.
20 |
21 | The objects in this collection should be consistent.
22 | We look at the properties on the first object for a baseline.
23 |
24 | .PARAMETER Right
25 | 'Right' collection of objects to join.
26 |
27 | The objects in this collection should be consistent.
28 | We look at the properties on the first object for a baseline.
29 |
30 | .PARAMETER LeftJoinProperty
31 | Property on Left collection objects that we match up with RightJoinProperty on the Right collection
32 |
33 | .PARAMETER RightJoinProperty
34 | Property on Right collection objects that we match up with LeftJoinProperty on the Left collection
35 |
36 | .PARAMETER LeftProperties
37 | One or more properties to keep from Left. Default is to keep all Left properties (*).
38 |
39 | Each property can:
40 | - Be a plain property name like "Name"
41 | - Contain wildcards like "*"
42 | - Be a Hashtable like @{Name="Product Name";Expression={$_.Name}}.
43 | Name is the output property name
44 | Expression is the property value ($_ as the current object)
45 |
46 | Alternatively, use the Suffix or Prefix parameter to avoid collisions
47 | Each property using this Hashtable syntax will be excluded from suffixes and prefixes
48 |
49 | .PARAMETER RightProperties
50 | One or more properties to keep from Right. Default is to keep all Right properties (*).
51 |
52 | Each property can:
53 | - Be a plain property name like "Name"
54 | - Contain wildcards like "*"
55 | - Be a Hashtable like @{Name="Product Name";Expression={$_.Name}}.
56 | Name is the output property name
57 | Expression is the property value ($_ as the current object)
58 |
59 | Alternatively, use the Suffix or Prefix parameter to avoid collisions
60 | Each property using this Hashtable syntax will be excluded from suffixes and prefixes
61 |
62 | .PARAMETER Prefix
63 | If specified, prepend Right object property names with this prefix to avoid collisions
64 |
65 | Example:
66 | Property Name = 'Name'
67 | Suffix = 'j_'
68 | Resulting Joined Property Name = 'j_Name'
69 |
70 | .PARAMETER Suffix
71 | If specified, append Right object property names with this suffix to avoid collisions
72 |
73 | Example:
74 | Property Name = 'Name'
75 | Suffix = '_j'
76 | Resulting Joined Property Name = 'Name_j'
77 |
78 | .PARAMETER Type
79 | Type of join. Default is AllInLeft.
80 |
81 | AllInLeft will have all elements from Left at least once in the output, and might appear more than once
82 | if the where clause is true for more than one element in right, Left elements with matches in Right are
83 | preceded by elements with no matches.
84 | SQL equivalent: outer left join (or simply left join)
85 |
86 | AllInRight is similar to AllInLeft.
87 |
88 | OnlyIfInBoth will cause all elements from Left to be placed in the output, only if there is at least one
89 | match in Right.
90 | SQL equivalent: inner join (or simply join)
91 |
92 | AllInBoth will have all entries in right and left in the output. Specifically, it will have all entries
93 | in right with at least one match in left, followed by all entries in Right with no matches in left,
94 | followed by all entries in Left with no matches in Right.
95 | SQL equivalent: full join
96 |
97 | .EXAMPLE
98 | #
99 | #Define some input data.
100 |
101 | $l = 1..5 | Foreach-Object {
102 | [PSCustomObject]@{
103 | Name = "jsmith$_"
104 | Birthday = (Get-Date).adddays(-1)
105 | }
106 | }
107 |
108 | $r = 4..7 | Foreach-Object{
109 | [PSCustomObject]@{
110 | Department = "Department $_"
111 | Name = "Department $_"
112 | Manager = "jsmith$_"
113 | }
114 | }
115 |
116 | #We have a name and Birthday for each manager, how do we find their department, using an inner join?
117 | Join-Object -Left $l -Right $r -LeftJoinProperty Name -RightJoinProperty Manager -Type OnlyIfInBoth -RightProperties Department
118 |
119 |
120 | # Name Birthday Department
121 | # ---- -------- ----------
122 | # jsmith4 4/14/2015 3:27:22 PM Department 4
123 | # jsmith5 4/14/2015 3:27:22 PM Department 5
124 |
125 | .EXAMPLE
126 | #
127 | #Define some input data.
128 |
129 | $l = 1..5 | Foreach-Object {
130 | [PSCustomObject]@{
131 | Name = "jsmith$_"
132 | Birthday = (Get-Date).adddays(-1)
133 | }
134 | }
135 |
136 | $r = 4..7 | Foreach-Object{
137 | [PSCustomObject]@{
138 | Department = "Department $_"
139 | Name = "Department $_"
140 | Manager = "jsmith$_"
141 | }
142 | }
143 |
144 | #We have a name and Birthday for each manager, how do we find all related department data, even if there are conflicting properties?
145 | $l | Join-Object -Right $r -LeftJoinProperty Name -RightJoinProperty Manager -Type AllInLeft -Prefix j_
146 |
147 | # Name Birthday j_Department j_Name j_Manager
148 | # ---- -------- ------------ ------ ---------
149 | # jsmith1 4/14/2015 3:27:22 PM
150 | # jsmith2 4/14/2015 3:27:22 PM
151 | # jsmith3 4/14/2015 3:27:22 PM
152 | # jsmith4 4/14/2015 3:27:22 PM Department 4 Department 4 jsmith4
153 | # jsmith5 4/14/2015 3:27:22 PM Department 5 Department 5 jsmith5
154 |
155 | .EXAMPLE
156 | #
157 | #Hey! You know how to script right? Can you merge these two CSVs, where Path1's IP is equal to Path2's IP_ADDRESS?
158 |
159 | #Get CSV data
160 | $s1 = Import-CSV $Path1
161 | $s2 = Import-CSV $Path2
162 |
163 | #Merge the data, using a full outer join to avoid omitting anything, and export it
164 | Join-Object -Left $s1 -Right $s2 -LeftJoinProperty IP_ADDRESS -RightJoinProperty IP -Prefix 'j_' -Type AllInBoth |
165 | Export-CSV $MergePath -NoTypeInformation
166 |
167 | .EXAMPLE
168 | #
169 | # "Hey Warren, we need to match up SSNs to Active Directory users, and check if they are enabled or not.
170 | # I'll e-mail you an unencrypted CSV with all the SSNs from gmail, what could go wrong?"
171 |
172 | # Import some SSNs.
173 | $SSNs = Import-CSV -Path D:\SSNs.csv
174 |
175 | #Get AD users, and match up by a common value, SamAccountName in this case:
176 | Get-ADUser -Filter "SamAccountName -like 'WFrame*'" |
177 | Join-Object -LeftJoinProperty SamAccountName -Right $SSNs `
178 | -RightJoinProperty SamAccountName -RightProperties ssn `
179 | -LeftProperties SamAccountName, enabled, ObjectClass
180 |
181 | .NOTES
182 | This borrows from:
183 | Dave Wyatt's Join-Object - http://powershell.org/wp/forums/topic/merging-very-large-collections/
184 | Lucio Silveira's Join-Object - http://blogs.msdn.com/b/powershell/archive/2012/07/13/join-object.aspx
185 |
186 | Changes:
187 | Always display full set of properties
188 | Display properties in order (left first, right second)
189 | If specified, add suffix or prefix to right object property names to avoid collisions
190 | Use a Hashtable rather than OrderedDictionary (avoid case sensitivity)
191 |
192 | .LINK
193 | http://ramblingcookiemonster.github.io/Join-Object/
194 |
195 | .FUNCTIONALITY
196 | PowerShell Language
197 |
198 | #>
199 | [CmdletBinding(DefaultParameterSetName = 'Default')]
200 | Param
201 | (
202 | [Parameter(Mandatory = $true, ValueFromPipeLine = $true)]
203 | $Left,
204 | [Parameter(Mandatory = $true)]
205 | $Right,
206 |
207 | [Parameter(Mandatory = $true)]
208 | [string[]]$LeftJoinProperty,
209 | [Parameter(Mandatory = $true)]
210 | [string[]]$RightJoinProperty,
211 |
212 | $LeftJoinScript,
213 | $RightJoinScript,
214 |
215 | [ValidateScript( { $_ -is [Collections.Hashtable] -or $_ -is [string] -or $_ -is [Collections.Specialized.OrderedDictionary] } )]
216 | $LeftProperties = '*',
217 | [ValidateScript( { $_ -is [Collections.Hashtable] -or $_ -is [string] -or $_ -is [Collections.Specialized.OrderedDictionary] } )]
218 | $RightProperties = '*',
219 |
220 | [string[]]$ExcludeLeftProperties,
221 | [string[]]$ExcludeRightProperties,
222 |
223 | [switch]$KeepRightJoinProperty,
224 |
225 | [ValidateSet('AllInLeft', 'OnlyIfInBoth', 'AllInBoth')]
226 | [Parameter(Mandatory = $false)]
227 | [string]$Type = 'AllInLeft',
228 |
229 | [string]$Prefix,
230 | [string]$Suffix,
231 |
232 | [Parameter(Mandatory, ParameterSetName = 'PassThru')]
233 | [switch]$PassThru,
234 | [Parameter(Mandatory, ParameterSetName = 'DataTable')]
235 | [switch]$DataTable,
236 | [Parameter(ParameterSetName = 'PassThru')]
237 | [Parameter(ParameterSetName = 'DataTable')]
238 | [Hashtable]$DataTableTypes,
239 |
240 | [ValidateSet('SingleOnly', 'DuplicateLines', 'SubGroups')]
241 | [string]$LeftMultiMode = 'SingleOnly',
242 | [ValidateSet('SingleOnly', 'DuplicateLines', 'SubGroups')]
243 | [string]$RightMultiMode = 'SingleOnly',
244 |
245 | [String]$AddKey,
246 | [switch]$AllowColumnsMerging,
247 | [Collections.Generic.IEqualityComparer[Object]]$Comparer
248 | )
249 | #region Validate Params
250 | if ($PassThru -and ($Type -in ('AllInBoth', 'OnlyIfInBoth') -or ($Type -eq 'AllInLeft' -and $RightMultiMode -eq 'DuplicateLines'))) {
251 | $PSCmdlet.ThrowTerminatingError(
252 | [Management.Automation.ErrorRecord]::new(
253 | [ArgumentException]::new('"-PassThru" compatible only with "-Type AllInLeft" with "-RightMultiMode" "SingleOnly" or "SubGroups"'),
254 | 'Incompatible Arguments',
255 | [Management.Automation.ErrorCategory]::InvalidArgument,
256 | $Type
257 | )
258 | )
259 | }
260 |
261 | if ($AddKey -and $Type -ne 'AllInBoth') {
262 | $PSCmdlet.ThrowTerminatingError(
263 | [Management.Automation.ErrorRecord]::new(
264 | [ArgumentException]::new('"-AddKey" support only "-Type AllInBoth"'),
265 | 'Incompatible Arguments',
266 | [Management.Automation.ErrorCategory]::InvalidArgument,
267 | $AddKey
268 | )
269 | )
270 | }
271 |
272 | if ($Type -in 'AllInLeft', 'OnlyIfInBoth') {
273 | if ($PSBoundParameters['LeftMultiMode'] -ne 'DuplicateLines' -and $null -ne $PSBoundParameters['LeftMultiMode']) {
274 | $PSCmdlet.ThrowTerminatingError(
275 | [Management.Automation.ErrorRecord]::new(
276 | [ArgumentException]::new('"-Type AllInLeft" and "-Type OnlyIfInBoth" support only "-LeftMultiMode DuplicateLines"'),
277 | 'Incompatible Arguments',
278 | [Management.Automation.ErrorCategory]::InvalidArgument,
279 | $Type
280 | )
281 | )
282 | }
283 | $Attributes = (Get-Variable 'LeftMultiMode').Attributes
284 | $null = $Attributes.Remove($Attributes.Where( { $_.TypeId.Name -eq 'ValidateSetAttribute' } )[0])
285 | $ValidateSetAttribute = [System.Management.Automation.ValidateSetAttribute]::new('SingleOnly', 'DuplicateLines', 'SubGroups', $null)
286 | $Attributes.Add($ValidateSetAttribute)
287 | $LeftMultiMode = $null
288 | }
289 | if ($Type -in 'OnlyIfInBoth') {
290 | if ($PSBoundParameters['RightMultiMode'] -ne 'DuplicateLines' -and $null -ne $PSBoundParameters['RightMultiMode']) {
291 | $PSCmdlet.ThrowTerminatingError(
292 | [Management.Automation.ErrorRecord]::new(
293 | [ArgumentException]::new('"-Type OnlyIfInBoth" support only "-RightMultiMode DuplicateLines"'),
294 | 'Incompatible Arguments',
295 | [Management.Automation.ErrorCategory]::InvalidArgument,
296 | $Type
297 | )
298 | )
299 | }
300 | $Attributes = (Get-Variable 'RightMultiMode').Attributes
301 | $null = $Attributes.Remove($Attributes.Where( { $_.TypeId.Name -eq 'ValidateSetAttribute' } )[0])
302 | $ValidateSetAttribute = [System.Management.Automation.ValidateSetAttribute]::new('SingleOnly', 'DuplicateLines', 'SubGroups', $null)
303 | $Attributes.Add($ValidateSetAttribute)
304 | $RightMultiMode = $null
305 | }
306 |
307 | if ($AllowColumnsMerging -and !$DataTable -and !($PassThru -and $Left -is [DataTable])) {
308 | $PSCmdlet.ThrowTerminatingError(
309 | [Management.Automation.ErrorRecord]::new(
310 | [ArgumentException]::new('"-AllowColumnsMerging" support only on DataTable output'),
311 | 'Incompatible Arguments',
312 | [Management.Automation.ErrorCategory]::InvalidArgument,
313 | $AllowColumnsMerging
314 | )
315 | )
316 | }
317 | #endregion Validate Params
318 | #region Set $SelectedLeftProperties and $SelectedRightProperties
319 | function Get-Properties {
320 | [CmdletBinding()]
321 | param
322 | (
323 | $Object,
324 | $SelectProperties,
325 | $ExcludeProperties,
326 | $Prefix,
327 | $Suffix
328 | )
329 | $Properties = [ordered]@{ }
330 | if ($Object -is [System.Data.DataTable]) {
331 | $ObjectProperties = $Object.Columns.ColumnName
332 | }
333 | else {
334 | $ObjectProperties = $Object[0].PSObject.Properties.Name
335 | }
336 | if ($SelectProperties -is [Hashtable] -or $SelectProperties -is [Collections.Specialized.OrderedDictionary]) {
337 | $SelectProperties.GetEnumerator() | Where-Object { $_.Key -notin $ExcludeProperties } | ForEach-Object { $Properties.Add($_.Key, $Prefix + $_.Value + $Suffix) }
338 | }
339 | elseif ($SelectProperties -eq '*') {
340 | $ObjectProperties | Where-Object { $_ -notin $ExcludeProperties } | ForEach-Object { $Properties.Add($_, $Prefix + $_ + $Suffix) }
341 | }
342 | else {
343 | $SelectProperties | Where-Object { $_ -notin $ExcludeProperties } | ForEach-Object { $Properties.Add($_, $Prefix + $_ + $Suffix) }
344 | }
345 | $Properties
346 | }
347 |
348 | $SelectedLeftProperties = Get-Properties -Object $Left -SelectProperties $LeftProperties -ExcludeProperties $ExcludeLeftProperties
349 | if (!$KeepRightJoinProperty) {
350 | $ExcludeRightProperties = @($ExcludeRightProperties) + @($RightJoinProperty) -ne $null
351 | }
352 | $SelectedRightProperties = Get-Properties -Object $Right -SelectProperties $RightProperties -ExcludeProperties $ExcludeRightProperties -Prefix $Prefix -Suffix $Suffix
353 | #endregion Set $SelectedLeftProperties and $SelectedRightProperties
354 | #region Importing package MoreLinq
355 | if ($Type -eq 'AllInBoth') {
356 | try {
357 | if ($PSScriptRoot) {
358 | $ScriptRoot = $PSScriptRoot
359 | }
360 | elseif ($psISE.CurrentFile.IsUntitled -eq $false) {
361 | $ScriptRoot = Split-Path -Path $psISE.CurrentFile.FullPath
362 | }
363 | elseif ($null -ne $psEditor.GetEditorContext().CurrentFile.Path -and $psEditor.GetEditorContext().CurrentFile.Path -notlike 'untitled:*') {
364 | $ScriptRoot = Split-Path -Path $psEditor.GetEditorContext().CurrentFile.Path
365 | }
366 | else {
367 | $ScriptRoot = '.'
368 | }
369 | if (!('MoreLinq.MoreEnumerable' -as [type])) {
370 | Add-Type -Path (Resolve-Path -Path "$ScriptRoot\morelinq.*\lib\netstandard2.0\MoreLinq.dll")
371 | }
372 | }
373 | catch {
374 | $PSCmdlet.ThrowTerminatingError(
375 | [Management.Automation.ErrorRecord]::new(
376 | [TypeLoadException]::new('Importing package MoreLinq failed: {0}' -f $_.Exception.Message, $_.Exception),
377 | 'Importing package',
378 | [Management.Automation.ErrorCategory]::NotInstalled,
379 | $null
380 | )
381 | )
382 | }
383 | }
384 | #endregion Importing package MoreLinq
385 | #region Set $RightJoinScript and $LeftJoinScript
386 | function Get-JoinScript {
387 | [CmdletBinding()]
388 | param
389 | (
390 | $JoinScript,
391 | $JoinProperty,
392 | $Side,
393 | $Object
394 | )
395 | if ($JoinScript) {
396 | if ($JoinScript.GetType().Name -eq 'Func`2') {
397 | $JoinScript #.GetNewClosure()
398 | }
399 | else {
400 | [System.Func[Object, String]]$JoinScript
401 | }
402 | }
403 | else {
404 | $JoinScript = if ($JoinProperty.Count -gt 1) {
405 | {
406 | param ($_Side_Line)
407 | ($_Side_Line | Select-Object -Property $_Side_JoinProperty).PSObject.Properties.Value
408 | }
409 | }
410 | else {
411 | if ($Object -is [Data.DataTable]) {
412 | {
413 | param ($_Side_Line)
414 | $_Side_Line[$_Side_JoinProperty]
415 | }
416 | }
417 | else {
418 | {
419 | param ($_Side_Line)
420 | $_Side_Line.$_Side_JoinProperty
421 | }
422 | }
423 | }
424 | [System.Func[Object, String]][Scriptblock]::Create($JoinScript.ToString().Replace('_Side_', $Side))
425 | }
426 | }
427 | $LeftJoinScript = Get-JoinScript -JoinScript $LeftJoinScript -JoinProperty $LeftJoinProperty -Side 'Left' -Object $Left
428 | $RightJoinScript = Get-JoinScript -JoinScript $RightJoinScript -JoinProperty $RightJoinProperty -Side 'Right' -Object $Right
429 | #endregion Set $RightJoinScript and $LeftJoinScript
430 | #region Prepare Data
431 | function Set-OutDataTable {
432 | param
433 | (
434 | $OutDataTable,
435 | $Object,
436 | $SelectedProperties,
437 | $AllowColumnsMerging
438 | )
439 | # Create Columns
440 | foreach ($item in $SelectedProperties.GetEnumerator()) {
441 | if (!$AllowColumnsMerging -or !$OutDataTable.Columns.Item($item.Value)) {
442 | if ($Object -is [Data.DataTable]) {
443 | $null = $OutDataTable.Columns.Add($item.Value, $Object.Columns.Item($item.Name).DataType)
444 | }
445 | else {
446 | if ($null -ne $DataTableTypes.($item.Value)) {
447 | $null = $OutDataTable.Columns.Add($item.Value, $DataTableTypes.($item.Value))
448 | }
449 | else {
450 | $null = $OutDataTable.Columns.Add($item.Value)
451 | }
452 | }
453 | }
454 | }
455 | }
456 | if ($DataTable) {
457 | $OutDataTable = [Data.DataTable]::new('Joined')
458 | if ($AddKey) {
459 | $null = $OutDataTable.Columns.Add($AddKey)
460 | }
461 | if ($LeftMultiMode -eq 'SubGroups') {
462 | $OutDataTableSubGroupTemplateLeft = [Data.DataTable]::new('LeftGroup')
463 | Set-OutDataTable -OutDataTable $OutDataTableSubGroupTemplateLeft -Object $Left -SelectedProperties $SelectedLeftProperties
464 | $null = $OutDataTable.Columns.Add('LeftGroup', [Object])
465 | }
466 | else {
467 | Set-OutDataTable -OutDataTable $OutDataTable -Object $Left -SelectedProperties $SelectedLeftProperties
468 | }
469 | if ($RightMultiMode -eq 'SubGroups') {
470 | $OutDataTableSubGroupTemplateRight = [Data.DataTable]::new('RightGroup')
471 | Set-OutDataTable -OutDataTable $OutDataTableSubGroupTemplateRight -Object $Right -SelectedProperties $SelectedRightProperties
472 | $null = $OutDataTable.Columns.Add('RightGroup', [Object])
473 | }
474 | else {
475 | Set-OutDataTable -OutDataTable $OutDataTable -Object $Right -SelectedProperties $SelectedRightProperties -AllowColumnsMerging $AllowColumnsMerging
476 | }
477 | }
478 | elseif ($PassThru) {
479 | if ($Left -is [Data.DataTable]) {
480 | # Remove LeftLine
481 | foreach ($ColumnName in $Left.Columns.ColumnName) {
482 | if ($ColumnName -notin $SelectedLeftProperties.Keys) {
483 | $Left.Columns.Remove($ColumnName)
484 | }
485 | }
486 | # Rename LeftLine
487 | foreach ($item in $SelectedLeftProperties.GetEnumerator()) {
488 | if ($item.Key -ne $item.value -and ($Column = $Left.Columns.Item($item.Key))) {
489 | $Column.ColumnName = $item.value
490 | }
491 | }
492 | if ($RightMultiMode -eq 'SubGroups') {
493 | $null = $Left.Columns.Add('RightGroup', [Object])
494 | }
495 | else {
496 | # Add RightLine to LeftLine
497 | foreach ($item in $SelectedRightProperties.GetEnumerator()) {
498 | if (!$AllowColumnsMerging -or !$Left.Columns.Item($item.Value)) {
499 | if ($null -ne $DataTableTypes.($item.Value)) {
500 | $null = $Left.Columns.Add($item.Value, $DataTableTypes.($item.Value))
501 | }
502 | else {
503 | $null = $Left.Columns.Add($item.Value)
504 | }
505 | }
506 | }
507 | }
508 | }
509 | if ($Right -is [Data.DataTable] -and $RightMultiMode -eq 'SubGroups') {
510 | $OutDataTableSubGroupTemplateRight = [Data.DataTable]::new('RightGroup')
511 | Set-OutDataTable -OutDataTable $OutDataTableSubGroupTemplateRight -Object $Right -SelectedProperties $SelectedRightProperties
512 | }
513 | }
514 | #endregion Prepare Data
515 | #region Main
516 | #region Main: Set $QueryParts
517 | $QueryParts = @{
518 | 'IfSideLine' = {
519 | if ($_Side_Line) {
520 | _SideScript_
521 | }
522 | }
523 | 'DataTableFromAny' = {
524 | foreach ($item in $Selected_Side_Properties.GetEnumerator()) {
525 | if ($null -ne ($Value = $_Side_Line.($item.Key))) {
526 | $_Row_[$item.Value] = $Value
527 | }
528 | }
529 | }
530 | 'DataTableFromDataTable' = {
531 | foreach ($item in $Selected_Side_Properties.GetEnumerator()) {
532 | if (($Value = $_Side_Line[$item.Key]) -isnot [DBNull]) {
533 | $_Row_[$item.Value] = $Value
534 | }
535 | }
536 | }
537 | 'DataTableFromSubGroup' = {
538 | if ($_Side_Lines) {
539 | _SubGroup_
540 | $_Row_['_Side_Group'] = $OutSubGroup_Side_
541 | }
542 | }
543 | 'SubGroupFromDataTable' = {
544 | $OutSubGroup_Side_ = $OutDataTableSubGroupTemplate_Side_.Clone()
545 | foreach ($_Side_Line in $_Side_Lines) {
546 | $RowSubGroup = $OutSubGroup_Side_.Rows.Add()
547 | _DataTable_
548 | }
549 | }
550 | 'SubGroupFromPSCustomObject' = {
551 | $OutSubGroup_Side_ = @()
552 | foreach ($_Side_Line in $_Side_Lines) {
553 | $RowSubGroup = [ordered]@{ }
554 | _PSCustomObject_
555 | $OutSubGroup_Side_ += [PSCustomObject]$RowSubGroup
556 | }
557 | }
558 | 'PSCustomObjectFromPSCustomObject' = {
559 | foreach ($item in $Selected_Side_Properties.GetEnumerator()) {
560 | $_Row_.Add($item.Value, $_Side_Line.($item.Key))
561 | }
562 | }
563 | 'PSCustomObjectFromAny' = {
564 | foreach ($item in $Selected_Side_Properties.GetEnumerator()) {
565 | if (($Value = $_Side_Line.($item.Key)) -is [DBNull]) {
566 | $Value = $null
567 | }
568 | $_Row_.Add($item.Value, $Value)
569 | }
570 | }
571 | 'PSCustomObjectFromSubGroup' = {
572 | if ($_Side_Lines) {
573 | _SubGroup_
574 | $_Row_.Add('_Side_Group', $OutSubGroup_Side_)
575 | }
576 | else {
577 | $_Row_.Add('_Side_Group', $null)
578 | }
579 | }
580 | }
581 |
582 | foreach ($Item in @($QueryParts.GetEnumerator())) {
583 | $QueryParts[$Item.Key] = $Item.Value.ToString()
584 | }
585 | #endregion Main: Set $QueryParts
586 | #region Main: Set $Query
587 | $Query = if ($PassThru) {
588 | if ($Left -is [Data.DataTable]) {
589 | $QueryTemp = @{
590 | Main = { _SidesScript_ }
591 | SideReplace = '_Row_', 'LeftLine'
592 | }
593 | if ($RightMultiMode -eq 'SubGroups') {
594 | $QueryTemp['SideSubGroupBase'] = $QueryParts['DataTableFromSubGroup']
595 | if ($Right -is [Data.DataTable]) {
596 | $QueryTemp['SideSubGroup'] = $QueryParts['SubGroupFromDataTable']
597 | }
598 | else {
599 | $QueryTemp['SideSubGroup'] = $QueryParts['SubGroupFromPSCustomObject']
600 | }
601 | }
602 | else {
603 | $QueryTemp['Right'] = { _DataTable_ }
604 | }
605 | $QueryTemp
606 | }
607 | else {
608 | # Left is PSCustomObject
609 | $QueryTemp = @{
610 | # Edit PSCustomObject
611 | Main = {
612 | # Add to LeftLine (Rename)
613 | foreach ($item in $SelectedLeftProperties.GetEnumerator()) {
614 | if ($item.Value -notin $LeftLine.PSObject.Properties.Name) {
615 | $LeftLine.PSObject.Properties.Add([Management.Automation.PSNoteProperty]::new($item.Value, $LeftLine.($item.Key)))
616 | }
617 | }
618 | # Remove from LeftLine
619 | foreach ($item in $LeftLine.PSObject.Properties.Name) {
620 | if ($item -notin $SelectedLeftProperties.Values) {
621 | $LeftLine.PSObject.Properties.Remove($item)
622 | }
623 | }
624 | _SidesScript_
625 | }
626 | SideReplace = '_Row_\.Add([^\r\n]*)', 'LeftLine.PSObject.Properties.Add([Management.Automation.PSNoteProperty]::new$1)'
627 | }
628 | if ($RightMultiMode -eq 'SubGroups') {
629 | $QueryTemp['SideSubGroupBase'] = $QueryParts['PSCustomObjectFromSubGroup']
630 | if ($Right -is [Data.DataTable]) {
631 | $QueryTemp['SideSubGroup'] = $QueryParts['SubGroupFromDataTable']
632 | }
633 | else {
634 | $QueryTemp['SideSubGroup'] = $QueryParts['SubGroupFromPSCustomObject']
635 | }
636 | }
637 | else {
638 | $QueryTemp['Right'] = { _PSCustomObject_ }
639 | }
640 | $QueryTemp
641 | }
642 | }
643 | elseif ($DataTable) {
644 | $QueryTemp = @{
645 | Main = {
646 | $RowMain = $OutDataTable.Rows.Add()
647 | _SidesScript_
648 | }
649 | SideReplace = '_Row_', 'RowMain'
650 | }
651 | if ($LeftMultiMode -eq 'SubGroups' -or $RightMultiMode -eq 'SubGroups') {
652 | $QueryTemp['SideSubGroupBase'] = $QueryParts['DataTableFromSubGroup']
653 | $QueryTemp['SideSubGroup'] = $QueryParts['SubGroupFromDataTable']
654 | }
655 | if ($LeftMultiMode -ne 'SubGroups' -or $RightMultiMode -ne 'SubGroups') {
656 | $QueryTemp['Side'] = { _DataTable_ }
657 | }
658 | $QueryTemp
659 | }
660 | else {
661 | # PSCustomObject
662 | $QueryTemp = @{
663 | Main = {
664 | $RowMain = [ordered]@{ }
665 | _SidesScript_
666 | [PSCustomObject]$RowMain
667 | }
668 | SideReplace = '_Row_', 'RowMain'
669 | }
670 | if ($LeftMultiMode -eq 'SubGroups' -or $RightMultiMode -eq 'SubGroups') {
671 | $QueryTemp['SideSubGroupBase'] = $QueryParts['PSCustomObjectFromSubGroup']
672 | $QueryTemp['SideSubGroup'] = $QueryParts['SubGroupFromPSCustomObject']
673 | }
674 | if ($LeftMultiMode -ne 'SubGroups' -or $RightMultiMode -ne 'SubGroups') {
675 | $QueryTemp['Side'] = { _PSCustomObject_ }
676 | }
677 | $QueryTemp
678 | }
679 |
680 | $Query['Base'] = {
681 | param(
682 | #_$Key_,
683 | $LeftLine,
684 | $RightLine
685 | )
686 | }
687 |
688 | foreach ($Item in @($Query.GetEnumerator())) {
689 | if ($Item.Value -is [Scriptblock]) {
690 | $Query[$Item.Key] = $Item.Value.ToString()
691 | }
692 | }
693 | #endregion Main: Set $Query
694 | #region Main: Assemble $Query
695 | function Invoke-AssembledQuery {
696 | param (
697 | $MultiMode,
698 | $Side,
699 | $Object
700 | )
701 | if ($MultiMode -eq 'SingleOnly') {
702 | $Query[$Side + 'Enumerable'] = { $_Side_Line = [System.Linq.Enumerable]::SingleOrDefault($_Side_Line) }
703 | }
704 | elseif ($MultiMode -eq 'DuplicateLines') {
705 | $Query[$Side + 'Enumerable'] = { $_Side_Lines = [System.Linq.Enumerable]::DefaultIfEmpty($_Side_Line) }
706 | $Query['Main'] = {
707 | foreach ($_Side_Line in $_Side_Lines) {
708 | _MainScript_
709 | }
710 | }.ToString().Replace('_Side_', $Side).Replace('_MainScript_', $Query['Main'])
711 | }
712 | if ($MultiMode -eq 'SubGroups') {
713 | $Query[$Side + 'Enumerable'] = { $_Side_Lines = if ($_Side_Line.Count) { $_Side_Line } }
714 | $Query[$Side] = if ($Object -is [Data.DataTable]) {
715 | ($Query['SideSubGroupBase'] -Replace $Query['SideReplace']).Replace('_SubGroup_', $Query['SideSubGroup']).Replace('_DataTable_', $QueryParts['DataTableFromDataTable']).Replace('_PSCustomObject_', $QueryParts['PSCustomObjectFromAny'])
716 | }
717 | else {
718 | ($Query['SideSubGroupBase'] -Replace $Query['SideReplace']).Replace('_SubGroup_', $Query['SideSubGroup']).Replace('_DataTable_', $QueryParts['DataTableFromAny']).Replace('_PSCustomObject_', $QueryParts['PSCustomObjectFromPSCustomObject'])
719 | }
720 | $Query[$Side] = $Query[$Side].Replace('_Row_', 'RowSubGroup')
721 | }
722 | else {
723 | if ($Query[$Side] -or $Query['Side']) {
724 | if ($null -eq $Query[$Side]) {
725 | $Query[$Side] = $Query['Side']
726 | }
727 | if ($null -ne $MultiMode -and $Query[$Side] -like '*_DataTable_*') {
728 | $Query[$Side] = $QueryParts['IfSideLine'].Replace('_SideScript_', $Query[$Side])
729 | }
730 | $Query[$Side] = if ($Object -is [Data.DataTable]) {
731 | $Query[$Side].Replace('_DataTable_', $QueryParts['DataTableFromDataTable']).Replace('_PSCustomObject_', $QueryParts['PSCustomObjectFromAny'])
732 | }
733 | else {
734 | $Query[$Side].Replace('_DataTable_', $QueryParts['DataTableFromAny']).Replace('_PSCustomObject_', $QueryParts['PSCustomObjectFromPSCustomObject'])
735 | }
736 | $Query[$Side] = $Query[$Side] -Replace $Query['SideReplace']
737 | }
738 | }
739 | if ($Query[$Side]) {
740 | $Query[$Side] = $Query[$Side].Replace('_Side_', $Side)
741 | }
742 | if ($Query[$Side + 'Enumerable']) {
743 | $Query[$Side + 'Enumerable'] = $Query[$Side + 'Enumerable'].ToString().Replace('_Side_', $Side)
744 | }
745 | }
746 | Invoke-AssembledQuery -MultiMode $LeftMultiMode -Side 'Left' -Object $Left
747 | Invoke-AssembledQuery -MultiMode $RightMultiMode -Side 'Right' -Object $Right
748 |
749 | if ($AddKey) {
750 | $KeyScript = {
751 | $RowMain._Key_ = $Key
752 | _SidesScript_
753 | }.ToString()
754 | $KeyScript = if ($DataTable) {
755 | $KeyScript.Replace('._Key_', '[$AddKey]')
756 | }
757 | else {
758 | $KeyScript.Replace('_Key_', '$AddKey')
759 | }
760 | $Query['Main'] = $Query['Main'].Replace('_SidesScript_', $KeyScript)
761 | }
762 |
763 | $Query['Main'] = $Query['Main'].Replace('_SidesScript_', $Query['Left'] + $Query['Right'])
764 |
765 | if ($Type -eq 'OnlyIfInBoth') {
766 | [System.Func[System.Object, System.Object, System.Object]]$Query = [Scriptblock]::Create($Query['Base'] + $Query['Main'])
767 | }
768 | elseif ($Type -eq 'AllInLeft') {
769 | [System.Func[System.Object, [Collections.Generic.IEnumerable[System.Object]], System.Object]]$Query = [Scriptblock]::Create($Query['Base'] + $Query['RightEnumerable'] + $Query['Main'])
770 | }
771 | elseif ($Type -eq 'AllInBoth') {
772 | [System.Func[System.Object, [Collections.Generic.IEnumerable[System.Object]], [Collections.Generic.IEnumerable[System.Object]], System.Object]]$Query = [Scriptblock]::Create($Query['Base'].Replace('#_$Key_', '$Key') + $Query['LeftEnumerable'] + "`n" + $Query['RightEnumerable'] + $Query['Main'])
773 | }
774 | #endregion Main: Assemble $Query
775 | #endregion Main
776 | #region Execute
777 | if ($Left -is [Data.DataTable]) {
778 | $LeftNew = [DataTableExtensions]::AsEnumerable($Left)
779 | }
780 | elseif ($Left -is [PSCustomObject] -or $Left -is [Collections.ArrayList]) {
781 | $LeftNew = @($Left)
782 | }
783 | else {
784 | $LeftNew = $Left
785 | }
786 | if ($Right -is [Data.DataTable]) {
787 | $RightNew = [DataTableExtensions]::AsEnumerable($Right)
788 | }
789 | elseif ($Right -is [PSCustomObject] -or $Right -is [Collections.ArrayList]) {
790 | $RightNew = @($Right)
791 | }
792 | else {
793 | $RightNew = $Right
794 | }
795 |
796 | try {
797 | $Result = if ($Type -eq 'OnlyIfInBoth') {
798 | [System.Linq.Enumerable]::ToArray(
799 | [System.Linq.Enumerable]::Join($LeftNew, $RightNew, $LeftJoinScript, $RightJoinScript, $query, $Comparer)
800 | )
801 | }
802 | elseif ($Type -eq 'AllInBoth') {
803 | [System.Linq.Enumerable]::ToArray(
804 | [MoreLinq.MoreEnumerable]::FullGroupJoin($LeftNew, $RightNew, $LeftJoinScript, $RightJoinScript, $query, $Comparer)
805 | )
806 | }
807 | else {
808 | [System.Linq.Enumerable]::ToArray(
809 | [System.Linq.Enumerable]::GroupJoin($LeftNew, $RightNew, $LeftJoinScript, $RightJoinScript, $query, $Comparer)
810 | )
811 | }
812 |
813 | if ($PassThru) {
814 | , $Left
815 | }
816 | elseif ($DataTable) {
817 | , $OutDataTable
818 | }
819 | elseif ($LeftMultiMode -eq 'DuplicateLines' -or $RightMultiMode -eq 'DuplicateLines') {
820 | $Result.ForEach( { $_ } )
821 | }
822 | else {
823 | $Result
824 | }
825 | }
826 | catch {
827 | $PSCmdlet.ThrowTerminatingError($_)
828 | }
829 | #endregion Execute
830 | }
--------------------------------------------------------------------------------
/Join-Object.psd1:
--------------------------------------------------------------------------------
1 | #
2 | # Module manifest for module 'Join-Object'
3 | #
4 | # Generated by: ili
5 | #
6 | # Generated on: 06/03/2018
7 | #
8 |
9 | @{
10 |
11 | # Script module or binary module file associated with this manifest.
12 | RootModule = 'Join-Object.psm1'
13 |
14 | # Version number of this module.
15 | ModuleVersion = '2.0.3'
16 |
17 | # Supported PSEditions
18 | # CompatiblePSEditions = @()
19 |
20 | # ID used to uniquely identify this module
21 | GUID = 'a7f9497a-ec8e-46e6-ad3a-39201b151458'
22 |
23 | # Author of this module
24 | Author = 'ili'
25 |
26 | # Company or vendor of this module
27 | #CompanyName = 'Unknown'
28 |
29 | # Copyright statement for this module
30 | Copyright = '(c) 2017 ili. All rights reserved.'
31 |
32 | # Description of the functionality provided by this module
33 | Description = @'
34 | Join-Object LINQ Edition.
35 | Join data from two sets of objects based on a common value.
36 | Aims to provide the exact functionality of https://github.com/RamblingCookieMonster/PowerShell/blob/master/Join-Object.ps1 with much better performance.
37 | Initial testing shows at last 100 times faster.
38 |
39 | More info at https://github.com/ili101/Join-Object/blob/master/README.md
40 | '@
41 |
42 | # Minimum version of the Windows PowerShell engine required by this module
43 | # PowerShellVersion = ''
44 |
45 | # Name of the Windows PowerShell host required by this module
46 | # PowerShellHostName = ''
47 |
48 | # Minimum version of the Windows PowerShell host required by this module
49 | # PowerShellHostVersion = ''
50 |
51 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
52 | # DotNetFrameworkVersion = ''
53 |
54 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
55 | # CLRVersion = ''
56 |
57 | # Processor architecture (None, X86, Amd64) required by this module
58 | # ProcessorArchitecture = ''
59 |
60 | # Modules that must be imported into the global environment prior to importing this module
61 | # RequiredModules = @()
62 |
63 | # Assemblies that must be loaded prior to importing this module
64 | # RequiredAssemblies = @()
65 |
66 | # Script files (.ps1) that are run in the caller's environment prior to importing this module.
67 | # ScriptsToProcess = @()
68 |
69 | # Type files (.ps1xml) to be loaded when importing this module
70 | # TypesToProcess = @()
71 |
72 | # Format files (.ps1xml) to be loaded when importing this module
73 | # FormatsToProcess = @()
74 |
75 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
76 | # NestedModules = @()
77 |
78 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
79 | FunctionsToExport = 'Join-Object'
80 |
81 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
82 | CmdletsToExport = @()
83 |
84 | # Variables to export from this module
85 | # VariablesToExport = ''
86 |
87 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
88 | AliasesToExport = @()
89 |
90 | # DSC resources to export from this module
91 | # DscResourcesToExport = @()
92 |
93 | # List of all modules packaged with this module
94 | # ModuleList = @()
95 |
96 | # List of all files packaged with this module
97 | # FileList = @()
98 |
99 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData Hashtable with additional module metadata used by PowerShell.
100 | PrivateData = @{
101 |
102 | PSData = @{
103 |
104 | # Tags applied to this module. These help with module discovery in online galleries.
105 | Tags = @('Join', 'Join-Object', 'LINQ', 'Combine', 'Merge', 'Union', 'Compare', 'Table', 'DataTable')
106 |
107 | # A URL to the license for this module.
108 | # LicenseUri = ''
109 |
110 | # A URL to the main website for this project.
111 | ProjectUri = 'https://github.com/ili101/Join-Object'
112 |
113 | # A URL to an icon representing this module.
114 | # IconUri = ''
115 |
116 | # ReleaseNotes of this module
117 | ReleaseNotes = @'
118 | Change Log
119 | https://github.com/ili101/Join-Object/blob/master/CHANGELOG.md
120 | '@
121 |
122 | } # End of PSData Hashtable
123 |
124 | } # End of PrivateData Hashtable
125 |
126 | # HelpInfo URI of this module
127 | # HelpInfoURI = ''
128 |
129 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
130 | # DefaultCommandPrefix = ''
131 |
132 | }
--------------------------------------------------------------------------------
/Join-Object.psm1:
--------------------------------------------------------------------------------
1 | #Get-ChildItem -Path $PSScriptRoot | Unblock-File
2 | Get-ChildItem -Path "$PSScriptRoot\*.ps1" -Exclude 'Class.ps1', 'Install.ps1' | ForEach-Object { . $_.FullName }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2020 Illy Metuki
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Join-Object
2 | Join-Object LINQ Edition.
3 | Aims to provide the exact functionality of https://github.com/RamblingCookieMonster/PowerShell/blob/master/Join-Object.ps1 with much better performance.
4 | Initial testing shows at last 100 times faster.
5 |
6 | [](https://www.powershellgallery.com/packages/Join-Object/)
7 | [](https://www.powershellgallery.com/packages/Join-Object/)
8 |
9 | | CI System | Environment | Master | Beta |
10 | |--------------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
11 | | AppVeyor | Windows, Core preview, Ubuntu | [](https://ci.appveyor.com/project/ili101/Join-Object) | [](https://ci.appveyor.com/project/ili101/Join-Object) |
12 | | Azure DevOps | Windows | [](https://dev.azure.com/ili101/Join-Object/_build/latest?definitionId=1&branchName=master) | [](https://dev.azure.com/ili101/Join-Object/_build/latest?definitionId=1&branchName=Beta) |
13 | | Azure DevOps | Windows (Core) | [](https://dev.azure.com/ili101/Join-Object/_build/latest?definitionId=1&branchName=master) | [](https://dev.azure.com/ili101/Join-Object/_build/latest?definitionId=1&branchName=Beta) |
14 | | Azure DevOps | Ubuntu | [](https://dev.azure.com/ili101/Join-Object/_build/latest?definitionId=1&branchName=master) | [](https://dev.azure.com/ili101/Join-Object/_build/latest?definitionId=1&branchName=Beta) |
15 | | Azure DevOps | macOS | [](https://dev.azure.com/ili101/Join-Object/_build/latest?definitionId=1&branchName=master) | [](https://dev.azure.com/ili101/Join-Object/_build/latest?definitionId=1&branchName=Beta) |
16 |
17 | ## Explanation and usage Examples
18 | See RamblingCookieMonster guide http://ramblingcookiemonster.github.io/Join-Object/ and [Join-Object.Examples.ps1](https://github.com/ili101/Join-Object/blob/master/Examples/Join-Object.Examples.ps1).
19 | And also [Join-Object.Tests.ps1](https://github.com/ili101/Join-Object/blob/master/Tests/Join-Object.Tests.ps1).
20 |
21 | ``` PowerShell
22 | # Left Object Example
23 | $PSCustomObject = @(
24 | [PSCustomObject]@{ID = 1 ; Sub = 'S1' ; IntO = 6}
25 | [PSCustomObject]@{ID = 2 ; Sub = 'S2' ; IntO = 7}
26 | [PSCustomObject]@{ID = 3 ; Sub = 'S3' ; IntO = $null}
27 | )
28 | # Right Object Example (DataTable)
29 | $DataTable = [Data.DataTable]::new('Test')
30 | $null = $DataTable.Columns.Add('IDD', [System.Int32])
31 | $null = $DataTable.Columns.Add('Name')
32 | $null = $DataTable.Columns.Add('Junk')
33 | $null = $DataTable.Columns.Add('IntT', [System.Int32])
34 | $null = $DataTable.Rows.Add(1, 'foo', 'AAA', 123456)
35 | $null = $DataTable.Rows.Add(3, 'Bar', 'S3', $null)
36 | $null = $DataTable.Rows.Add(4, 'D', $null, $null)
37 |
38 | # Join the 2 together ("Left Join" in this example)
39 | Join-Object -Left $PSCustomObject -Right $DataTable -LeftJoinProperty 'ID' -RightJoinProperty 'IDD' -ExcludeRightProperties 'Junk' -Prefix 'R_' | Format-Table
40 |
41 | <# Output
42 | ID Sub IntO R_Name R_IntT
43 | -- --- ---- ------ ------
44 | 1 S1 6 foo 123456
45 | 2 S2 7
46 | 3 S3 Bar
47 | #>
48 | ```
49 |
50 | ## Additional functionality
51 | * Supports DataTable object type.
52 | * Additional parameters **-ExcludeLeftProperties** and **-ExcludeRightProperties**.
53 | * Additional parameter **-PassThru**, If added changes the original Left Object
54 | * Converts DBNull to $null
55 | * **-RightJoinScript** and **-LeftJoinScript** parameters to support custom joining scripts.
56 | * -RightJoinProperty and -LeftJoinProperty supports multiple Properties (String Array) to **join on multiple columns**.
57 | * **-DataTable** parameter to output as "DataTable".
58 | * **-AddKey** can be used with "-Type AllInBoth" to add a column containing the joining key.
59 | * **-AllowColumnsMerging** Allow duplicate columns in the Left and Right Objects, will overwrite the conflicting Left data with the Right data (Ignoring Nulls), Supported only on DataTable output for now.
60 | * **-Comparer** allow use of custom [EqualityComparer].
61 |
62 | ## Missing functionality
63 | * -Type "AllInRight".
64 |
65 | ## To do
66 | * Noting for now, You can open an Issues if something is needed.
67 |
68 | ## Install
69 | From repository (Recommended)
70 | ```PowerShell
71 | Install-Module -Name Join-Object -Scope CurrentUser
72 | ```
73 | From GitHub Branch (For testing)
74 | ```PowerShell
75 | $Uri = 'https://raw.githubusercontent.com/ili101/Join-Object/master/Install.ps1'; & ([Scriptblock]::Create((irm $Uri))) -FromGitHub $Uri
76 | ```
77 |
78 | ## Contributing
79 | If you fund a bug or added functionality or anything else just fork and send pull requests. Thank you!
80 |
81 | ## Changelog
82 | [CHANGELOG.md](https://github.com/ili101/Join-Object/blob/master/CHANGELOG.md)
83 |
84 | ## Default and supported join modes
85 | | Join Type | Linq mode | LeftMultiMode supported | RightMultiMode supported | PassThru |
86 | |---------------|---------------|-------------------------------------------|-------------------------------------------|-----------------------|
87 | | OnlyIfInBoth | Join | **DuplicateLines** | **DuplicateLines** | Not Supported |
88 | | **AllInLeft** | GroupJoin | **DuplicateLines** | **SingleOnly**, DuplicateLines, SubGroups | SingleOnly, SubGroups |
89 | | AllInBoth | FullGroupJoin | **SingleOnly**, DuplicateLines, SubGroups | **SingleOnly**, DuplicateLines, SubGroups | Not Supported |
90 |
91 | \* **Bold** signifies the default setting.
92 |
93 | # More PowerShell stuff
94 | https://github.com/ili101/PowerShell
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. AllInBoth. DataTable.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. AllInBoth. DataTable.xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. AllInBoth.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. AllInBoth.xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. DataTable. (DataTableTypes).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. DataTable. (DataTableTypes).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. DataTable. (Error, Mismatch).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. DataTable. (Error, Mismatch).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. DataTable.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. DataTable.xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. OnlyIfInBoth. DataTable.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. OnlyIfInBoth. DataTable.xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. OnlyIfInBoth.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. OnlyIfInBoth.xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. PassThru. (DataTableTypes).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. PassThru. (DataTableTypes).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. PassThru.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject. PassThru.xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTable - PSCustomObject.xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTable - PSCustomObjectJunk. DataTable. (AllowColumnsMerging).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTable - PSCustomObjectJunk. DataTable. (AllowColumnsMerging).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTable - PSCustomObjectJunk. PassThru. (AllowColumnsMerging).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTable - PSCustomObjectJunk. PassThru. (AllowColumnsMerging).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTable - PSCustomObjectKeyArray. (Comparer).xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | System.Object[]
5 | System.Array
6 | System.Object
7 |
8 |
9 |
10 |
11 | System.Management.Automation.PSCustomObject
12 | System.Object
13 |
14 |
15 | 1
16 | A
17 | AAA
18 | 5
19 | S1
20 | 6
21 |
22 |
23 |
24 |
25 |
26 | 3
27 | C
28 | S3
29 |
30 | S1
31 | 6
32 |
33 |
34 |
35 |
36 |
37 | 4
38 | D
39 |
40 |
41 | S4
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTable - PSCustomObjectMulti. (SubArray).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTable - PSCustomObjectMulti. (SubArray).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTable - [DataTable]PSCustomObjectJunk. PassThru. (AllowColumnsMerging).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTable - [DataTable]PSCustomObjectJunk. PassThru. (AllowColumnsMerging).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTableMulti - PSCustomObjectMulti. AllInBoth. (SubGroups).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTableMulti - PSCustomObjectMulti. AllInBoth. (SubGroups).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTableMulti - PSCustomObjectMulti. AllInBoth. DataTable. (DuplicateLines).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTableMulti - PSCustomObjectMulti. AllInBoth. DataTable. (DuplicateLines).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTableMulti - PSCustomObjectMulti. AllInBoth. DataTable. (SubGroups).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTableMulti - PSCustomObjectMulti. AllInBoth. DataTable. (SubGroups).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTableMulti - PSCustomObjectMulti. AllInLeft. PassThru. (SubGroups).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTableMulti - PSCustomObjectMulti. AllInLeft. PassThru. (SubGroups).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, DataTableMulti - [DataTable]PSCustomObjectMulti. AllInLeft. PassThru. (SubGroups).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, DataTableMulti - [DataTable]PSCustomObjectMulti. AllInLeft. PassThru. (SubGroups).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. (DBNull to $null).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. (DBNull to $null).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. (Error, Mismatch).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. (Error, Mismatch).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. (JoinScript String).xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | System.Object[]
5 | System.Array
6 | System.Object
7 |
8 |
9 |
10 |
11 | System.Management.Automation.PSCustomObject
12 | System.Object
13 |
14 |
15 | 1
16 | S1
17 | A
18 | 5
19 |
20 |
21 |
22 |
23 |
24 | 2
25 | S2
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | 3
34 | S3
35 | C
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. (JoinScript).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. (JoinScript).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. (KeepRightJoinProperty).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. (KeepRightJoinProperty).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. (Multi Join).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. (Multi Join).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. (Ordered).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. (Ordered).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. AllInBoth. DataTable.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. AllInBoth. DataTable.xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. AllInBoth.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. AllInBoth.xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. DataTable.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. DataTable.xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. OnlyIfInBoth. DataTable.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. OnlyIfInBoth. DataTable.xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. OnlyIfInBoth.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. OnlyIfInBoth.xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. PassThru.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable. PassThru.xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObject - DataTable.xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObjectMulti - DataTable. (SubArray).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObjectMulti - DataTable. (SubArray).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInBoth. (SubGroups Key).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInBoth. (SubGroups Key).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInBoth. (SubGroups).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInBoth. (SubGroups).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInBoth. DataTable. (DuplicateLines).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInBoth. DataTable. (DuplicateLines).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInBoth. DataTable. (SubGroups Key).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInBoth. DataTable. (SubGroups Key).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInBoth. DataTable. (SubGroups).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInBoth. DataTable. (SubGroups).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInLeft. PassThru. (SubGroups).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObjectMulti - DataTableMulti. AllInLeft. PassThru. (SubGroups).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObjectMulti - [PSCustomObject]DataTableMulti. AllInLeft. PassThru. (SubGroups).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObjectMulti - [PSCustomObject]DataTableMulti. AllInLeft. PassThru. (SubGroups).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, PSCustomObject[0] - DataTable. (Single).xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/CompareData/DataSetSmall, PSCustomObject[0] - DataTable. (Single).xml
--------------------------------------------------------------------------------
/Tests/CompareData/DataSetSmall, [Collections.ArrayList]PSCustomObject - DataTable.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | System.Object[]
5 | System.Array
6 | System.Object
7 |
8 |
9 |
10 |
11 | System.Management.Automation.PSCustomObject
12 | System.Object
13 |
14 |
15 | 1
16 | S1
17 | A
18 | 5
19 |
20 |
21 |
22 |
23 |
24 | 2
25 | S2
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | 3
34 | S3
35 | C
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Tests/Join-Object.Tests.Big.ps1:
--------------------------------------------------------------------------------
1 | #Requires -Modules Pester
2 | #Requires -Modules PSSQLite
3 |
4 | if ($PSScriptRoot) {
5 | $ScriptRoot = $PSScriptRoot
6 | }
7 | elseif ($psISE.CurrentFile.IsUntitled -eq $false) {
8 | $ScriptRoot = Split-Path -Path $psISE.CurrentFile.FullPath
9 | }
10 | elseif ($null -ne $psEditor.GetEditorContext().CurrentFile.Path -and $psEditor.GetEditorContext().CurrentFile.Path -notlike 'untitled:*') {
11 | $ScriptRoot = Split-Path -Path $psEditor.GetEditorContext().CurrentFile.Path
12 | }
13 | else {
14 | $ScriptRoot = '.'
15 | }
16 | . "$ScriptRoot\TestHelpers.ps1"
17 |
18 | $DataSetBig10k = "$ScriptRoot\TestDataSetBig10k.db"
19 | $DataSetBig100k = "$ScriptRoot\TestDataSetBig100k.db"
20 |
21 | Describe -Name 'Join-Object' -Fixture {
22 | Context -Name ($DataSetName = 'DataSetBig100k') -Fixture {
23 | It -Name "Testing: " -TestCases @(
24 | Format-Test @{
25 | Params = @{
26 | Left = @{ Table = 'authors' ; As = 'DataTable' }
27 | Right = @{ Table = 'posts' ; As = 'DataTable' }
28 | LeftJoinProperty = 'author_id'
29 | RightJoinProperty = 'author_id'
30 | #DataTable = $true
31 | #RightMultiMode = 'SubGroups'
32 | }
33 | }
34 | ) -Test {
35 | param (
36 | $Params,
37 | $DataSet,
38 | $TestName,
39 | $RunScript,
40 | $ExpectedErrorMessage,
41 | [ValidateSet('Test', 'Run')]
42 | $ExpectedErrorOn
43 | )
44 | if ($RunScript) {
45 | . $RunScript
46 | }
47 |
48 | # Load Data
49 | try {
50 | $DbConnection = New-SQLiteConnection -DataSource $DataSet
51 | $Params.Left = Get-Params -Param $Params.Left -DbConnection $DbConnection
52 | $Params.Right = Get-Params -Param $Params.Right -DbConnection $DbConnection
53 | }
54 | finally {
55 | $DbConnection.Dispose()
56 | }
57 |
58 | # Execute Cmdlet
59 | $Measure = Measure-Command {
60 | $JoinedOutput = Join-Object @Params
61 | }
62 |
63 | if ($JoinedOutput -is [Data.DataTable]) {
64 | Write-Host ("Execution Time: {0}, Count: {1}, Type: {2}." -f $Measure, $JoinedOutput.Rows.Count, $JoinedOutput.GetType())
65 | Write-Host ('Sample:' + ($JoinedOutput.Rows[$JoinedOutput.Rows.Count - 1] | Out-String).TrimEnd())
66 | }
67 | else {
68 | Write-Host ("Execution Time: {0}, Count: {1}, Type: {2}." -f $Measure, $JoinedOutput.Count, $JoinedOutput.GetType())
69 | Write-Host ('Sample:' + ($JoinedOutput[-1] | Out-String).TrimEnd())
70 | }
71 | }
72 | }
73 | }
74 |
75 | <# http://filldb.info
76 | CREATE TABLE `authors` (
77 | `author_id` INT(11) NOT NULL AUTO_INCREMENT,
78 | `first_name` VARCHAR(50) NOT NULL COLLATE 'utf8_unicode_ci',
79 | `last_name` VARCHAR(50) NOT NULL COLLATE 'utf8_unicode_ci',
80 | `email` VARCHAR(100) NOT NULL COLLATE 'utf8_unicode_ci',
81 | `birthdate` DATE NOT NULL,
82 | `added` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
83 | PRIMARY KEY (`author_id`),
84 | UNIQUE INDEX `email` (`email`)
85 | )
86 | COLLATE='utf8_unicode_ci'
87 | ENGINE=InnoDB;
88 |
89 | CREATE TABLE `posts` (
90 | `post_id` INT(11) NOT NULL AUTO_INCREMENT,
91 | `author_id` INT(11) NOT NULL,
92 | `title` VARCHAR(255) NOT NULL COLLATE 'utf8_unicode_ci',
93 | `description` VARCHAR(500) NOT NULL COLLATE 'utf8_unicode_ci',
94 | `content` TEXT NOT NULL COLLATE 'utf8_unicode_ci',
95 | `date` DATE NOT NULL,
96 | PRIMARY KEY (`post_id`)
97 | )
98 | COLLATE='utf8_unicode_ci'
99 | ENGINE=InnoDB;
100 | #>
101 | <# SQLite
102 | DROP TABLE IF EXISTS `authors`;
103 | CREATE TABLE `authors` (
104 | `author_id` int(11) NOT NULL,
105 | `first_name` varchar(50) NOT NULL,
106 | `last_name` varchar(50) NOT NULL,
107 | `email` varchar(100) NOT NULL UNIQUE,
108 | `birthdate` date NOT NULL,
109 | `added` timestamp NOT NULL DEFAULT current_timestamp,
110 | PRIMARY KEY (`author_id`)
111 | );
112 | DROP TABLE IF EXISTS `posts`;
113 | CREATE TABLE `posts` (
114 | `post_id` int(11) NOT NULL,
115 | `author_id` int(11) NOT NULL,
116 | `title` varchar(255) NOT NULL,
117 | `description` varchar(500) NOT NULL,
118 | `content` text NOT NULL,
119 | `date` date NOT NULL,
120 | PRIMARY KEY (`post_id`)
121 | );
122 | #>
--------------------------------------------------------------------------------
/Tests/Join-Object.Tests.ps1:
--------------------------------------------------------------------------------
1 | #Requires -Modules Pester
2 | #Requires -Modules @{ ModuleName = 'Assert' ; ModuleVersion = '999.9.5' }
3 | [CmdletBinding()]
4 | Param
5 | (
6 | [Switch]$SaveMode,
7 | [String[]]$FilterTests
8 | )
9 | $EquivalencyOption = Get-EquivalencyOption -Comparator 'StrictEquality'-StrictOrder
10 |
11 | if ($PSScriptRoot) {
12 | $ScriptRoot = $PSScriptRoot
13 | }
14 | elseif ($psISE.CurrentFile.IsUntitled -eq $false) {
15 | $ScriptRoot = Split-Path -Path $psISE.CurrentFile.FullPath
16 | }
17 | elseif ($null -ne $psEditor.GetEditorContext().CurrentFile.Path -and $psEditor.GetEditorContext().CurrentFile.Path -notlike 'untitled:*') {
18 | $ScriptRoot = Split-Path -Path $psEditor.GetEditorContext().CurrentFile.Path
19 | }
20 | else {
21 | $ScriptRoot = '.'
22 | }
23 | . "$ScriptRoot\TestHelpers.ps1"
24 |
25 | $DataSetSmall = {
26 | $PSCustomObject = @(
27 | [PSCustomObject]@{ ID = 1 ; Sub = 'S1' ; IntO = 6 }
28 | [PSCustomObject]@{ ID = 2 ; Sub = 'S2' ; IntO = 7 }
29 | [PSCustomObject]@{ ID = 3 ; Sub = 'S3' ; IntO = $null }
30 | )
31 |
32 | $PSCustomObjectJunk = @(
33 | [PSCustomObject]@{ ID = 1 ; Sub = 'S1' ; IntO = 6 ; Junk = 'New1' }
34 | [PSCustomObject]@{ ID = 2 ; Sub = 'S2' ; IntO = 7 ; Junk = $null }
35 | [PSCustomObject]@{ ID = 3 ; Sub = 'S3' ; IntO = $null ; Junk = $null }
36 | )
37 |
38 | $PSCustomObjectKeyArray = @(
39 | [PSCustomObject]@{ID = 1, 2, 3 ; Sub = 'S1' ; IntO = 6 }
40 | [PSCustomObject]@{ID = 4 ; Sub = 'S4' ; IntO = $null }
41 | )
42 |
43 | $DataTable = [Data.DataTable]::new('Test')
44 | $null = $DataTable.Columns.Add('IDD', [System.Int32])
45 | $null = $DataTable.Columns.Add('Name')
46 | $null = $DataTable.Columns.Add('Junk')
47 | $null = $DataTable.Columns.Add('IntT', [System.Int32])
48 | $null = $DataTable.Rows.Add(1, 'A', 'AAA', 5)
49 | $null = $DataTable.Rows.Add(3, 'C', 'S3', $null)
50 | $null = $DataTable.Rows.Add(4, 'D', $null, $null)
51 |
52 | $PSCustomObjectMulti = @(
53 | [PSCustomObject]@{ ID = 1 ; Sub = 'S1' ; IntO = 6 }
54 | [PSCustomObject]@{ ID = 1 ; Sub = 'S12' ; IntO = 62 }
55 | [PSCustomObject]@{ ID = 2 ; Sub = 'S2' ; IntO = 7 }
56 | [PSCustomObject]@{ ID = 2 ; Sub = 'S22' ; IntO = 72 }
57 | )
58 |
59 | $DataTableMulti = [Data.DataTable]::new('Test')
60 | $null = $DataTableMulti.Columns.Add('IDD', [System.Int32])
61 | $null = $DataTableMulti.Columns.Add('Name')
62 | $null = $DataTableMulti.Columns.Add('Junk')
63 | $null = $DataTableMulti.Columns.Add('IntT', [System.Int32])
64 | $null = $DataTableMulti.Rows.Add(1, 'A', 'AAA', 5)
65 | $null = $DataTableMulti.Rows.Add(1, 'A2', 'AAA2', 52)
66 | $null = $DataTableMulti.Rows.Add(3, 'C', 'S3', $null)
67 | $null = $DataTableMulti.Rows.Add(3, 'C2', 'S32', $null)
68 | }
69 | #. $DataSetSmall
70 |
71 | Describe -Name 'Join-Object' -Fixture {
72 | Context -Name ($DataSetName = 'DataSetSmall') -Fixture {
73 | Class EqualityComparerMy : Collections.Generic.EqualityComparer[Object] {
74 | [bool] Equals([Object]$Object1 , [Object]$Object2) {
75 | if ($Object1 -contains $Object2 -or $Object1 -in $Object2) {
76 | return $true
77 | }
78 | else {
79 | return $false
80 | }
81 | }
82 | [int] GetHashCode([Object]$Object) {
83 | return 1
84 | }
85 | }
86 | $TestCases = @(
87 | Format-Test @{
88 | Description = 'Error, Mismatch'
89 | ExpectedErrorOn = 'Test'
90 | ExpectedErrorMessage = "Object{ID=3; R_IntT=; R_Name=X; Subscription=S3}'."
91 | Params = @{
92 | Left = 'PSCustomObject'
93 | Right = 'DataTable'
94 | LeftJoinProperty = 'ID'
95 | RightJoinProperty = 'IDD'
96 | LeftProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
97 | ExcludeRightProperties = 'Junk'
98 | Prefix = 'R_'
99 | }
100 | }
101 | Format-Test @{
102 | Description = 'Error, Mismatch'
103 | ExpectedErrorOn = 'Test'
104 | ExpectedErrorMessage = "but some values were missing: 'PSObject{IDD=3; IntT=; Junk=S3; Name=X; R_IntO=; R_Sub=S3}"
105 | Params = @{
106 | Left = 'DataTable'
107 | Right = 'PSCustomObject'
108 | LeftJoinProperty = 'IDD'
109 | RightJoinProperty = 'ID'
110 | ExcludeRightProperties = 'Junk'
111 | Prefix = 'R_'
112 | DataTable = $true
113 | }
114 | }
115 |
116 | Format-Test @{
117 | Params = @{
118 | Left = 'PSCustomObject'
119 | Right = 'DataTable'
120 | LeftJoinProperty = 'ID'
121 | RightJoinProperty = 'IDD'
122 | LeftProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
123 | ExcludeRightProperties = 'Junk'
124 | Prefix = 'R_'
125 | }
126 | }
127 | Format-Test @{
128 | Params = @{
129 | Left = 'DataTable'
130 | Right = 'PSCustomObject'
131 | LeftJoinProperty = 'IDD'
132 | RightJoinProperty = 'ID'
133 | RightProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
134 | ExcludeLeftProperties = 'Junk'
135 | Suffix = '_R'
136 | }
137 | }
138 | Format-Test @{
139 | Params = @{
140 | Left = 'PSCustomObject'
141 | Right = 'DataTable'
142 | LeftJoinProperty = 'ID'
143 | RightJoinProperty = 'IDD'
144 | LeftProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
145 | ExcludeRightProperties = 'Junk'
146 | Prefix = 'R_'
147 | Type = 'AllInBoth'
148 | }
149 | }
150 | Format-Test @{
151 | Params = @{
152 | Left = 'DataTable'
153 | Right = 'PSCustomObject'
154 | LeftJoinProperty = 'IDD'
155 | RightJoinProperty = 'ID'
156 | RightProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
157 | ExcludeLeftProperties = 'Junk'
158 | Suffix = '_R'
159 | Type = 'AllInBoth'
160 | }
161 | }
162 | Format-Test @{
163 | Params = @{
164 | Left = 'PSCustomObject'
165 | Right = 'DataTable'
166 | LeftJoinProperty = 'ID'
167 | RightJoinProperty = 'IDD'
168 | LeftProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
169 | ExcludeRightProperties = 'Junk'
170 | Prefix = 'R_'
171 | Type = 'OnlyIfInBoth'
172 | }
173 | }
174 | Format-Test @{
175 | Params = @{
176 | Left = 'DataTable'
177 | Right = 'PSCustomObject'
178 | LeftJoinProperty = 'IDD'
179 | RightJoinProperty = 'ID'
180 | RightProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
181 | ExcludeLeftProperties = 'Junk'
182 | Suffix = '_R'
183 | Type = 'OnlyIfInBoth'
184 | }
185 | }
186 |
187 | Format-Test @{
188 | Params = @{
189 | Left = 'PSCustomObject'
190 | Right = 'DataTable'
191 | LeftJoinProperty = 'ID'
192 | RightJoinProperty = 'IDD'
193 | LeftProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
194 | ExcludeRightProperties = 'Junk'
195 | Prefix = 'R_'
196 | PassThru = $true
197 | }
198 | }
199 | Format-Test @{
200 | Params = @{
201 | Left = 'DataTable'
202 | Right = 'PSCustomObject'
203 | LeftJoinProperty = 'IDD'
204 | RightJoinProperty = 'ID'
205 | LeftProperties = @{ IDD = 'IDD' ; Name = 'NewName' }
206 | RightProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
207 | ExcludeLeftProperties = 'Junk'
208 | Suffix = '_R'
209 | PassThru = $true
210 | }
211 | }
212 | Format-Test @{
213 | Description = 'Error, PassThru+AllInBoth'
214 | ExpectedErrorOn = 'Run'
215 | ExpectedErrorMessage = '"-PassThru" compatible only with "-Type AllInLeft" with "-RightMultiMode" "SingleOnly" or "SubGroups"'
216 | Params = @{
217 | Left = 'PSCustomObject'
218 | Right = 'DataTable'
219 | LeftJoinProperty = 'ID'
220 | RightJoinProperty = 'IDD'
221 | LeftProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
222 | ExcludeRightProperties = 'Junk'
223 | Prefix = 'R_'
224 | PassThru = $true
225 | Type = 'AllInBoth'
226 | }
227 | }
228 | # PassThru cannot add lines.
229 | Format-Test @{
230 | Description = 'Error, PassThru+AllInBoth'
231 | ExpectedErrorOn = 'Run'
232 | ExpectedErrorMessage = '"-PassThru" compatible only with "-Type AllInLeft" with "-RightMultiMode" "SingleOnly" or "SubGroups"'
233 | Params = @{
234 | Left = 'DataTable'
235 | Right = 'PSCustomObject'
236 | LeftJoinProperty = 'IDD'
237 | RightJoinProperty = 'ID'
238 | LeftProperties = @{ IDD = 'IDD' ; Name = 'NewName' }
239 | RightProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
240 | ExcludeLeftProperties = 'Junk'
241 | Suffix = '_R'
242 | PassThru = $true
243 | Type = 'AllInBoth'
244 | }
245 | }
246 | # PassThru cannot remove lines.
247 | Format-Test @{
248 | Description = 'Error, PassThru+OnlyIfInBoth'
249 | ExpectedErrorOn = 'Run'
250 | ExpectedErrorMessage = '"-PassThru" compatible only with "-Type AllInLeft" with "-RightMultiMode" "SingleOnly" or "SubGroups"'
251 | Params = @{
252 | Left = 'PSCustomObjectMulti'
253 | Right = 'DataTableMulti'
254 | LeftJoinProperty = 'ID'
255 | RightJoinProperty = 'IDD'
256 | ExcludeRightProperties = 'Junk'
257 | Prefix = 'R_'
258 | PassThru = $true
259 | Type = 'OnlyIfInBoth'
260 | }
261 | }
262 | # PassThru cannot duplicate lines.
263 | Format-Test @{
264 | Description = 'Error, PassThru+AllInLeft+RightDuplicate'
265 | ExpectedErrorOn = 'Run'
266 | ExpectedErrorMessage = '"-PassThru" compatible only with "-Type AllInLeft" with "-RightMultiMode" "SingleOnly" or "SubGroups"'
267 | Params = @{
268 | Left = 'PSCustomObjectMulti'
269 | Right = 'DataTableMulti'
270 | LeftJoinProperty = 'ID'
271 | RightJoinProperty = 'IDD'
272 | ExcludeRightProperties = 'Junk'
273 | Prefix = 'R_'
274 | PassThru = $true
275 | Type = 'AllInLeft'
276 | RightMultiMode = 'DuplicateLines'
277 | }
278 | }
279 |
280 | Format-Test @{
281 | Params = @{
282 | Left = 'DataTable'
283 | Right = 'PSCustomObject'
284 | LeftJoinProperty = 'IDD'
285 | RightJoinProperty = 'ID'
286 | ExcludeRightProperties = 'Junk'
287 | Prefix = 'R_'
288 | DataTable = $true
289 | }
290 | }
291 | Format-Test @{
292 | Params = @{
293 | Left = 'PSCustomObject'
294 | Right = 'DataTable'
295 | LeftJoinProperty = 'ID'
296 | RightJoinProperty = 'IDD'
297 | ExcludeRightProperties = 'Junk'
298 | Prefix = 'R_'
299 | DataTable = $true
300 | }
301 | }
302 | Format-Test @{
303 | Params = @{
304 | Left = 'PSCustomObject'
305 | Right = 'DataTable'
306 | LeftJoinProperty = 'ID'
307 | RightJoinProperty = 'IDD'
308 | ExcludeRightProperties = 'Junk'
309 | Prefix = 'R_'
310 | DataTable = $true
311 | Type = 'AllInBoth'
312 | }
313 | }
314 | Format-Test @{
315 | Params = @{
316 | Left = 'DataTable'
317 | Right = 'PSCustomObject'
318 | LeftJoinProperty = 'IDD'
319 | RightJoinProperty = 'ID'
320 | ExcludeRightProperties = 'Junk'
321 | Prefix = 'R_'
322 | DataTable = $true
323 | Type = 'AllInBoth'
324 | }
325 | }
326 | Format-Test @{
327 | Params = @{
328 | Left = 'PSCustomObject'
329 | Right = 'DataTable'
330 | LeftJoinProperty = 'ID'
331 | RightJoinProperty = 'IDD'
332 | ExcludeRightProperties = 'Junk'
333 | Prefix = 'R_'
334 | DataTable = $true
335 | Type = 'OnlyIfInBoth'
336 | }
337 | }
338 | Format-Test @{
339 | Params = @{
340 | Left = 'DataTable'
341 | Right = 'PSCustomObject'
342 | LeftJoinProperty = 'IDD'
343 | RightJoinProperty = 'ID'
344 | ExcludeRightProperties = 'Junk'
345 | Prefix = 'R_'
346 | DataTable = $true
347 | Type = 'OnlyIfInBoth'
348 | }
349 | }
350 |
351 | Format-Test @{
352 | Description = 'Ordered'
353 | Params = @{
354 | Left = 'PSCustomObject'
355 | Right = 'DataTable'
356 | LeftJoinProperty = 'ID'
357 | RightJoinProperty = 'IDD'
358 | LeftProperties = [Ordered]@{ Sub = 'Subscription' ; ID = 'ID' }
359 | ExcludeRightProperties = 'Junk'
360 | Prefix = 'R_'
361 | }
362 | }
363 | Format-Test @{
364 | Description = 'DBNull to $null'
365 | Params = @{
366 | Left = 'PSCustomObject'
367 | Right = 'DataTable'
368 | LeftJoinProperty = 'ID'
369 | RightJoinProperty = 'IDD'
370 | }
371 | }
372 | Format-Test @{
373 | Description = 'Single'
374 | Params = @{
375 | Left = 'PSCustomObject[0]'
376 | Right = 'DataTable'
377 | LeftJoinProperty = 'ID'
378 | RightJoinProperty = 'IDD'
379 | LeftProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
380 | ExcludeRightProperties = 'Junk'
381 | Prefix = 'R_'
382 | }
383 | }
384 | Format-Test @{
385 | Description = 'KeepRightJoinProperty'
386 | Params = @{
387 | Left = 'PSCustomObject'
388 | Right = 'DataTable'
389 | LeftJoinProperty = 'ID'
390 | RightJoinProperty = 'IDD'
391 | LeftProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
392 | ExcludeRightProperties = 'Junk'
393 | Prefix = 'R_'
394 | KeepRightJoinProperty = $true
395 | }
396 | }
397 | Format-Test @{
398 | Description = 'Multi Join'
399 | Params = @{
400 | Left = 'PSCustomObject'
401 | Right = 'DataTable'
402 | LeftJoinProperty = 'ID', 'Sub'
403 | RightJoinProperty = 'IDD', 'Junk'
404 | LeftProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
405 | ExcludeRightProperties = 'Junk'
406 | Prefix = 'R_'
407 | }
408 | }
409 | Format-Test @{
410 | Description = 'JoinScript'
411 | Params = @{
412 | Left = 'PSCustomObject'
413 | Right = 'DataTable'
414 | LeftJoinProperty = 'Sub'
415 | RightJoinProperty = 'IDD'
416 | LeftProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
417 | ExcludeRightProperties = 'Junk'
418 | Prefix = 'R_'
419 | LeftJoinScript = { param ($Line) ($Line.Sub).Replace('S', 'X') }
420 | RightJoinScript = { param ($Line) 'X' + $Line.IDD }
421 | }
422 | }
423 | Format-Test @{
424 | Description = 'JoinScript String'
425 | Params = @{
426 | Left = 'PSCustomObject'
427 | Right = 'DataTable'
428 | LeftJoinProperty = 'Sub'
429 | RightJoinProperty = 'IDD'
430 | LeftProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
431 | ExcludeRightProperties = 'Junk'
432 | Prefix = 'R_'
433 | LeftJoinScript = { param ($Line) ($Line.Sub).Replace('S', '') }
434 | }
435 | }
436 | Format-Test @{
437 | Description = 'DataTableTypes'
438 | Params = @{
439 | Left = 'DataTable'
440 | Right = 'PSCustomObject'
441 | LeftJoinProperty = 'IDD'
442 | RightJoinProperty = 'ID'
443 | ExcludeRightProperties = 'Junk'
444 | Prefix = 'R_'
445 | DataTable = $true
446 | DataTableTypes = @{ R_IntO = [Int] }
447 | }
448 | }
449 | Format-Test @{
450 | Description = 'DataTableTypes'
451 | Params = @{
452 | Left = 'DataTable'
453 | Right = 'PSCustomObject'
454 | LeftJoinProperty = 'IDD'
455 | RightJoinProperty = 'ID'
456 | ExcludeRightProperties = 'Junk'
457 | Prefix = 'R_'
458 | PassThru = $true
459 | DataTableTypes = @{ R_IntO = [Int] }
460 | }
461 | }
462 | Format-Test @{
463 | Description = 'Error, No AllowColumnsMerging'
464 | ExpectedErrorOn = 'Run'
465 | ExpectedErrorMessage = "Item has already been added. Key in dictionary: 'Junk'"
466 | Params = @{
467 | Left = 'DataTable'
468 | Right = 'PSCustomObjectJunk'
469 | LeftJoinProperty = 'IDD'
470 | RightJoinProperty = 'ID'
471 | }
472 | }
473 | Format-Test @{
474 | Description = 'Error, AllowColumnsMerging-DataTable'
475 | ExpectedErrorOn = 'Run'
476 | ExpectedErrorMessage = '"-AllowColumnsMerging" support only on DataTable output'
477 | Params = @{
478 | Left = 'DataTable'
479 | Right = 'PSCustomObjectJunk'
480 | LeftJoinProperty = 'IDD'
481 | RightJoinProperty = 'ID'
482 | AllowColumnsMerging = $true
483 | }
484 | }
485 | Format-Test @{
486 | Description = 'AllowColumnsMerging'
487 | Params = @{
488 | Left = 'DataTable'
489 | Right = 'PSCustomObjectJunk'
490 | LeftJoinProperty = 'IDD'
491 | RightJoinProperty = 'ID'
492 | AllowColumnsMerging = $true
493 | DataTable = $true
494 | }
495 | }
496 | Format-Test @{
497 | Description = 'Error, AllowColumnsMerging-OutDataTable'
498 | ExpectedErrorOn = 'Run'
499 | ExpectedErrorMessage = '"-AllowColumnsMerging" support only on DataTable output'
500 | Params = @{
501 | Left = 'PSCustomObjectJunk'
502 | Right = 'DataTable'
503 | LeftJoinProperty = 'IDD'
504 | RightJoinProperty = 'ID'
505 | AllowColumnsMerging = $true
506 | PassThru = $true
507 | }
508 | }
509 | Format-Test @{
510 | Description = 'AllowColumnsMerging'
511 | Params = @{
512 | Left = 'DataTable'
513 | Right = 'PSCustomObjectJunk'
514 | LeftJoinProperty = 'IDD'
515 | RightJoinProperty = 'ID'
516 | AllowColumnsMerging = $true
517 | PassThru = $true
518 | }
519 | }
520 | Format-Test @{
521 | Description = 'AllowColumnsMerging'
522 | Params = @{
523 | Left = 'DataTable'
524 | Right = '[DataTable]PSCustomObjectJunk'
525 | LeftJoinProperty = 'IDD'
526 | RightJoinProperty = 'ID'
527 | AllowColumnsMerging = $true
528 | PassThru = $true
529 | }
530 | }
531 | Format-Test @{
532 | Description = 'Comparer'
533 | Params = @{
534 | Left = 'DataTable'
535 | Right = 'PSCustomObjectKeyArray'
536 | LeftJoinProperty = 'IDD'
537 | RightJoinProperty = 'ID'
538 | ExcludeRightProperties = 'Junk'
539 | Prefix = 'R_'
540 | RightJoinScript = [System.Func[Object, Object]] { param ($Line) $Line.ID }
541 | Comparer = [EqualityComparerMy]::new()
542 | }
543 | }
544 |
545 | Format-Test @{
546 | Description = 'Error, SingleOnly+BasicJoin'
547 | ExpectedErrorOn = 'Run'
548 | ExpectedErrorMessage = '"-Type AllInLeft" and "-Type OnlyIfInBoth" support only "-LeftMultiMode DuplicateLines"'
549 | Params = @{
550 | Left = 'PSCustomObjectMulti'
551 | Right = 'DataTableMulti'
552 | LeftJoinProperty = 'ID'
553 | RightJoinProperty = 'IDD'
554 | ExcludeRightProperties = 'Junk'
555 | Prefix = 'R_'
556 | DataTable = $true
557 | Type = 'AllInLeft'
558 | LeftMultiMode = 'SingleOnly'
559 | }
560 | }
561 | Format-Test @{
562 | Description = 'Error, SingleOnly+BasicJoin'
563 | ExpectedErrorOn = 'Run'
564 | ExpectedErrorMessage = '"-Type OnlyIfInBoth" support only "-RightMultiMode DuplicateLines"'
565 | Params = @{
566 | Left = 'DataTableMulti'
567 | Right = 'PSCustomObjectMulti'
568 | LeftJoinProperty = 'IDD'
569 | RightJoinProperty = 'ID'
570 | Prefix = 'R_'
571 | DataTable = $true
572 | Type = 'OnlyIfInBoth'
573 | RightMultiMode = 'SingleOnly'
574 | }
575 | }
576 | Format-Test @{
577 | Description = 'Error, SingleOnly'
578 | ExpectedErrorOn = 'Run'
579 | ExpectedErrorMessage = 'Sequence contains more than one element'
580 | Params = @{
581 | Left = 'PSCustomObjectMulti'
582 | Right = 'DataTableMulti'
583 | LeftJoinProperty = 'ID'
584 | RightJoinProperty = 'IDD'
585 | ExcludeRightProperties = 'Junk'
586 | Prefix = 'R_'
587 | DataTable = $true
588 | Type = 'AllInLeft'
589 | RightMultiMode = 'SingleOnly'
590 | }
591 | }
592 | Format-Test @{
593 | Description = 'Error, SingleOnly'
594 | ExpectedErrorOn = 'Run'
595 | ExpectedErrorMessage = 'Sequence contains more than one element'
596 | Params = @{
597 | Left = 'PSCustomObjectMulti'
598 | Right = 'DataTableMulti'
599 | LeftJoinProperty = 'ID'
600 | RightJoinProperty = 'IDD'
601 | ExcludeRightProperties = 'Junk'
602 | Prefix = 'R_'
603 | DataTable = $true
604 | Type = 'AllInBoth'
605 | LeftMultiMode = 'SingleOnly'
606 | }
607 | }
608 | Format-Test @{
609 | Description = 'DuplicateLines'
610 | Params = @{
611 | Left = 'PSCustomObjectMulti'
612 | Right = 'DataTableMulti'
613 | LeftJoinProperty = 'ID'
614 | RightJoinProperty = 'IDD'
615 | ExcludeRightProperties = 'Junk'
616 | Prefix = 'R_'
617 | DataTable = $true
618 | Type = 'AllInBoth'
619 | LeftMultiMode = 'DuplicateLines'
620 | RightMultiMode = 'DuplicateLines'
621 | }
622 | }
623 | Format-Test @{
624 | Description = 'DuplicateLines'
625 | Params = @{
626 | Left = 'DataTableMulti'
627 | Right = 'PSCustomObjectMulti'
628 | LeftJoinProperty = 'IDD'
629 | RightJoinProperty = 'ID'
630 | Prefix = 'R_'
631 | DataTable = $true
632 | Type = 'AllInBoth'
633 | LeftMultiMode = 'DuplicateLines'
634 | RightMultiMode = 'DuplicateLines'
635 | }
636 | }
637 | Format-Test @{
638 | Description = 'SubGroups'
639 | Params = @{
640 | Left = 'PSCustomObjectMulti'
641 | Right = 'DataTableMulti'
642 | LeftJoinProperty = 'ID'
643 | RightJoinProperty = 'IDD'
644 | ExcludeRightProperties = 'Junk'
645 | Prefix = 'R_'
646 | DataTable = $true
647 | Type = 'AllInBoth'
648 | LeftMultiMode = 'SubGroups'
649 | RightMultiMode = 'SubGroups'
650 | }
651 | }
652 | Format-Test @{
653 | Description = 'SubGroups'
654 | Params = @{
655 | Left = 'DataTableMulti'
656 | Right = 'PSCustomObjectMulti'
657 | LeftJoinProperty = 'IDD'
658 | RightJoinProperty = 'ID'
659 | Prefix = 'R_'
660 | DataTable = $true
661 | Type = 'AllInBoth'
662 | LeftMultiMode = 'SubGroups'
663 | RightMultiMode = 'SubGroups'
664 | }
665 | }
666 | Format-Test @{
667 | Description = 'SubGroups'
668 | Params = @{
669 | Left = 'PSCustomObjectMulti'
670 | Right = 'DataTableMulti'
671 | LeftJoinProperty = 'ID'
672 | RightJoinProperty = 'IDD'
673 | ExcludeRightProperties = 'Junk'
674 | Prefix = 'R_'
675 | Type = 'AllInBoth'
676 | LeftMultiMode = 'SubGroups'
677 | RightMultiMode = 'SubGroups'
678 | }
679 | }
680 | Format-Test @{
681 | Description = 'SubGroups'
682 | Params = @{
683 | Left = 'DataTableMulti'
684 | Right = 'PSCustomObjectMulti'
685 | LeftJoinProperty = 'IDD'
686 | RightJoinProperty = 'ID'
687 | Prefix = 'R_'
688 | Type = 'AllInBoth'
689 | LeftMultiMode = 'SubGroups'
690 | RightMultiMode = 'SubGroups'
691 | }
692 | }
693 | Format-Test @{
694 | Description = 'SubGroups'
695 | Params = @{
696 | Left = 'PSCustomObjectMulti'
697 | Right = 'DataTableMulti'
698 | LeftJoinProperty = 'ID'
699 | RightJoinProperty = 'IDD'
700 | ExcludeRightProperties = 'Junk'
701 | Prefix = 'R_'
702 | Type = 'AllInLeft'
703 | RightMultiMode = 'SubGroups'
704 | PassThru = $true
705 | }
706 | }
707 | Format-Test @{
708 | Description = 'SubGroups'
709 | Params = @{
710 | Left = 'PSCustomObjectMulti'
711 | Right = '[PSCustomObject]DataTableMulti'
712 | LeftJoinProperty = 'ID'
713 | RightJoinProperty = 'IDD'
714 | ExcludeRightProperties = 'Junk'
715 | Prefix = 'R_'
716 | Type = 'AllInLeft'
717 | RightMultiMode = 'SubGroups'
718 | PassThru = $true
719 | }
720 | }
721 | Format-Test @{
722 | Description = 'SubGroups'
723 | Params = @{
724 | Left = 'DataTableMulti'
725 | Right = 'PSCustomObjectMulti'
726 | LeftJoinProperty = 'IDD'
727 | RightJoinProperty = 'ID'
728 | Prefix = 'R_'
729 | Type = 'AllInLeft'
730 | RightMultiMode = 'SubGroups'
731 | PassThru = $true
732 | }
733 | }
734 | Format-Test @{
735 | Description = 'SubGroups'
736 | Params = @{
737 | Left = 'DataTableMulti'
738 | Right = '[DataTable]PSCustomObjectMulti'
739 | LeftJoinProperty = 'IDD'
740 | RightJoinProperty = 'ID'
741 | Prefix = 'R_'
742 | Type = 'AllInLeft'
743 | RightMultiMode = 'SubGroups'
744 | PassThru = $true
745 | }
746 | }
747 | Format-Test @{
748 | Description = 'SubGroups Key'
749 | Params = @{
750 | Left = 'PSCustomObjectMulti'
751 | Right = 'DataTableMulti'
752 | LeftJoinProperty = 'ID'
753 | RightJoinProperty = 'IDD'
754 | ExcludeRightProperties = 'Junk'
755 | Prefix = 'R_'
756 | Type = 'AllInBoth'
757 | LeftMultiMode = 'SubGroups'
758 | RightMultiMode = 'SubGroups'
759 | AddKey = 'JoinKey'
760 | }
761 | }
762 | Format-Test @{
763 | Description = 'Error, AddKey-AllInBoth'
764 | ExpectedErrorOn = 'Run'
765 | ExpectedErrorMessage = '"-AddKey" support only "-Type AllInBoth"'
766 | Params = @{
767 | Left = 'PSCustomObjectMulti'
768 | Right = 'DataTableMulti'
769 | LeftJoinProperty = 'ID'
770 | RightJoinProperty = 'IDD'
771 | ExcludeRightProperties = 'Junk'
772 | Prefix = 'R_'
773 | LeftMultiMode = 'SubGroups'
774 | RightMultiMode = 'SubGroups'
775 | AddKey = 'JoinKey'
776 | }
777 | }
778 | Format-Test @{
779 | Description = 'SubGroups Key'
780 | Params = @{
781 | Left = 'PSCustomObjectMulti'
782 | Right = 'DataTableMulti'
783 | LeftJoinProperty = 'ID'
784 | RightJoinProperty = 'IDD'
785 | ExcludeRightProperties = 'Junk'
786 | ExcludeLeftProperties = 'ID'
787 | Prefix = 'R_'
788 | Type = 'AllInBoth'
789 | LeftMultiMode = 'DuplicateLines'
790 | RightMultiMode = 'SubGroups'
791 | AddKey = 'JoinKey'
792 | DataTable = $true
793 | KeepRightJoinProperty = $true
794 | }
795 | }
796 | Format-Test @{
797 | Description = 'SubArray'
798 | Params = @{
799 | Left = 'PSCustomObjectMulti'
800 | Right = 'DataTable'
801 | LeftJoinProperty = 'ID'
802 | RightJoinProperty = 'IDD'
803 | Prefix = 'R_'
804 | }
805 | }
806 | Format-Test @{
807 | Description = 'SubArray'
808 | #RunScript = { Set-ItResult -Pending -Because 'Bug?' }
809 | Params = @{
810 | Left = 'DataTable'
811 | Right = 'PSCustomObjectMulti'
812 | LeftJoinProperty = 'IDD'
813 | RightJoinProperty = 'ID'
814 | Prefix = 'R_'
815 | RightMultiMode = 'DuplicateLines'
816 | }
817 | }
818 | Format-Test @{
819 | Params = @{
820 | Left = '[Collections.ArrayList]PSCustomObject'
821 | Right = 'DataTable'
822 | LeftJoinProperty = 'ID'
823 | RightJoinProperty = 'IDD'
824 | LeftProperties = @{ ID = 'ID' ; Sub = 'Subscription' }
825 | ExcludeRightProperties = 'Junk'
826 | Prefix = 'R_'
827 | }
828 | }
829 | )
830 | if ($FilterTests) {
831 | $TestCases = $TestCases | Where-Object TestName -In $FilterTests
832 | }
833 | It -Name "Testing: " -TestCases $TestCases -Test {
834 | param (
835 | $Params,
836 | $DataSet,
837 | $TestName,
838 | $RunScript,
839 | $ExpectedErrorMessage,
840 | [ValidateSet('Test', 'Run')]
841 | $ExpectedErrorOn
842 | )
843 | if ($RunScript) {
844 | . $RunScript
845 | }
846 |
847 | # Load Data
848 | . $DataSet
849 | $Params.Left = Get-Params -Param $Params.Left
850 | $Params.Right = Get-Params -Param $Params.Right
851 |
852 | # Save Before Data Copy
853 | $BeforeLeft = [System.Management.Automation.PSSerializer]::Deserialize([System.Management.Automation.PSSerializer]::Serialize($Params.Left, 3))
854 | $BeforeRight = [System.Management.Automation.PSSerializer]::Deserialize([System.Management.Automation.PSSerializer]::Serialize($Params.Right, 3))
855 |
856 | # Execute Cmdlet
857 | if ($ExpectedErrorOn -eq 'Run') {
858 | { $JoinedOutput = Join-Object @Params } | Should -Throw -ExpectedMessage $ExpectedErrorMessage
859 | Continue
860 | }
861 | else {
862 | $JoinedOutput = Join-Object @Params
863 | }
864 | Write-Verbose ('it returns:' + ($JoinedOutput | Format-Table | Out-String))
865 |
866 | # Save CompareData (Xml)
867 | if ($SaveMode) {
868 | try {
869 | $CompareDataXml = (Get-Content -LiteralPath "$ScriptRoot\CompareData\$TestName.xml" -ErrorAction 'Stop') -join [Environment]::NewLine
870 | $CompareDataNew = [System.Management.Automation.PSSerializer]::Deserialize($CompareDataXml)
871 | }
872 | catch {
873 | $CompareDataNew = 'None'
874 | }
875 | Write-Host ("{0}`nLeft:{1}Right:{2}Params:{3}Result:{4}Replacing:{5}" -f $TestName,
876 | ($Params.Left | Format-Table | Out-String), ($Params.Right | Format-Table | Out-String),
877 | (([PSCustomObject]$Params) | Select-Object -ExcludeProperty 'Left', 'Right' -Property '*' | Format-List | Out-String),
878 | ($JoinedOutput | Format-Table | Out-String), ($CompareDataNew | Format-Table | Out-String)
879 | )
880 |
881 | if ($JoinedOutput -is [Array] -and ($SubArrayTest = $JoinedOutput | ForEach-Object { $_ -is [Array] }) -contains $true) {
882 | Write-Warning ("SubArrayTest $SubArrayTest")
883 | }
884 | Export-Clixml -LiteralPath "$ScriptRoot\CompareData\$TestName.xml" -InputObject $JoinedOutput -Depth 3 -Confirm
885 | }
886 |
887 | # Get CompareData
888 | $CompareDataXml = (Get-Content -LiteralPath "$ScriptRoot\CompareData\$TestName.xml") -join [Environment]::NewLine
889 | $CompareDataNew = [System.Management.Automation.PSSerializer]::Deserialize($CompareDataXml)
890 | Write-Verbose ('it should return:' + ($CompareDataNew | Format-Table | Out-String))
891 |
892 | # Test
893 | if ($ExpectedErrorOn -eq 'Test') {
894 | { Assert-Equivalent -Actual $JoinedOutput -Expected $CompareDataNew -Options $EquivalencyOption } | Should -Throw -ExpectedMessage $ExpectedErrorMessage
895 | }
896 | else {
897 | Assert-Equivalent -Actual $JoinedOutput -Expected $CompareDataNew -Options $EquivalencyOption
898 | }
899 |
900 | if ($Params.PassThru) {
901 | Assert-Equivalent -Actual $Params.Left -Expected $CompareDataNew -Options $EquivalencyOption
902 | }
903 | else {
904 | Assert-Equivalent -Actual $Params.Left -Expected $BeforeLeft -Options $EquivalencyOption
905 | }
906 | Assert-Equivalent -Actual $Params.Right -Expected $BeforeRight -Options $EquivalencyOption
907 |
908 | if (!$Params.PassThru) {
909 | if ($Params.DataTable) {
910 | Should -BeOfType -ActualValue $JoinedOutput -ExpectedType 'System.Data.DataTable'
911 | $JoinedOutput | Should -BeOfType -ExpectedType 'System.Data.DataRow'
912 | }
913 | else {
914 | if ($JoinedOutput.Count -gt 1) {
915 | Should -BeOfType -ActualValue $JoinedOutput -ExpectedType 'System.Array'
916 | }
917 | else {
918 | Should -BeOfType -ActualValue $JoinedOutput -ExpectedType 'PSCustomObject'
919 | }
920 | $JoinedOutput | Should -BeOfType -ExpectedType 'PSCustomObject'
921 | }
922 | }
923 | }
924 | }
925 | }
--------------------------------------------------------------------------------
/Tests/TestDataSetBig100K.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/TestDataSetBig100K.db
--------------------------------------------------------------------------------
/Tests/TestDataSetBig10k.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ili101/Join-Object/4aa6b018c731cbf7cb386d0f2c9b814802fc668a/Tests/TestDataSetBig10k.db
--------------------------------------------------------------------------------
/Tests/TestHelpers.ps1:
--------------------------------------------------------------------------------
1 | function ConvertFrom-DataTable {
2 | <#
3 | .SYNOPSIS
4 | Convert DataTable to PSCustomObject, Support Deserialized DataTable.
5 | #>
6 | [CmdletBinding()]
7 | param
8 | (
9 | [Parameter(Mandatory, ValueFromPipeline)]
10 | [ValidateScript( { $_ -is [System.Data.DataRow] } )]
11 | $InputObject
12 | )
13 | begin {
14 | $First = $true
15 | }
16 | process {
17 | if ($First) {
18 | $First = $false
19 | $Properties = if ($InputObject -is [System.Data.DataTable]) {
20 | $InputObject.Columns.ColumnName
21 | }
22 | else {
23 | $InputObject.PSObject.Properties.Name | Where-Object { $_ -notin ('RowError', 'RowState', 'Table', 'ItemArray', 'HasErrors') }
24 | }
25 | }
26 | foreach ($DataRow in $InputObject) {
27 | $RowHash = [ordered]@{ }
28 | foreach ($Property in $Properties) {
29 | if ($DataRow.$Property -is [DBNull]) {
30 | $RowHash[$Property] = $null
31 | }
32 | else {
33 | $RowHash[$Property] = $DataRow.$Property
34 | }
35 | }
36 | [PSCustomObject]$RowHash
37 | }
38 | }
39 | }
40 | function ConvertTo-DataTable {
41 | <#
42 | .SYNOPSIS
43 | Convert PSCustomObject to DataTable.
44 | Warning: Column type taken from firs line, null will be [Object].
45 | #>
46 | [CmdletBinding()]
47 | param(
48 | [Parameter(Mandatory, ValueFromPipeline)]
49 | $InputObject
50 | )
51 | begin {
52 | $OutputDataTable = [Data.DataTable]::new()
53 | $First = $true
54 | }
55 | process {
56 | foreach ($PSCustomObject in $InputObject) {
57 | if ($First) {
58 | $First = $false
59 | foreach ($Property in $PSCustomObject.PSObject.Properties) {
60 | $null = $OutputDataTable.Columns.Add($Property.Name, $Property.TypeNameOfValue)
61 | }
62 | }
63 | $null = $OutputDataTable.Rows.Add($PSCustomObject.PSObject.Properties.Value)
64 | }
65 | }
66 | end {
67 | , $OutputDataTable
68 | }
69 | }
70 |
71 | function Format-Test {
72 | <#
73 | .SYNOPSIS
74 | Adds "TestName" and "DataSet" to the test.
75 | Assumes $DataSetName and its value exists.
76 | #>
77 | [CmdletBinding()]
78 | param
79 | (
80 | [Parameter(Mandatory)]
81 | [Hashtable]$Test
82 | )
83 | if ($DataSetName, $Test.Params.Left, $Test.Params.Right -contains $null) {
84 | Throw 'Missing param'
85 | }
86 |
87 | if ($Test.Params.Left -is [Hashtable]) { $Test.Params.Left = [PSCustomObject]$Test.Params.Left }
88 | if ($Test.Params.Right -is [Hashtable]) { $Test.Params.Right = [PSCustomObject]$Test.Params.Right }
89 |
90 | $Data = '{0}, {1} - {2}' -f $DataSetName, $Test.Params.Left, $Test.Params.Right
91 | $OutputType = switch ($Test.Params) {
92 | { $_.DataTable } { 'DataTable' }
93 | { $_.PassThru } { 'PassThru' }
94 | }
95 | $OutputType = if ($OutputType) {
96 | '{0}' -f ($OutputType -join '&')
97 | }
98 | $Description = if ($Test.Description) {
99 | '({0})' -f $Test.Description
100 | }
101 |
102 | $Test.TestName = (($Data, $Test.Params.Type, $OutputType, $Description) -ne $null) -join '. '
103 | $Test.DataSet = Get-Variable -Name $DataSetName -ValueOnly
104 | $Test
105 | }
106 | function Get-Params {
107 | <#
108 | .SYNOPSIS
109 | Get Data to test.
110 | If not DbConnection Param is String containing variable name, It will be fetched and optionally converted and selected on.
111 | If DbConnection Param is Hashtable with "Table" to get and "As" to set output type.
112 | #>
113 | [CmdletBinding()]
114 | param
115 | (
116 | [Parameter(Mandatory)]
117 | $Param,
118 | [System.Data.Common.DbConnection]
119 | $DbConnection
120 | )
121 | if ($DbConnection) {
122 | $Output = Invoke-SqliteQuery -SQLiteConnection $DbConnection -Query ('SELECT * FROM {0}' -f $Param.Table) -As $Param.As
123 | , $Output
124 | }
125 | else {
126 | $ParamSplit = $Param -split '(?<=\])(?