Closed theramiyer closed 4 years ago
Thank you. I think that we should add support for Hashtable/OrderedDictionary to it as well.
Also, please fix $Array+= by changing it to GenericList -> https://evotec.xyz/powershell-few-tricks-about-hashtable-and-array-i-wish-i-knew-when-i-started/ - it's not best practice to use += anymore.
Ah, wish I'd known that before! Please see if this is OK. I tested it to be working as expected.
We'll work on adding hashtable/OD support as well, in the future?
The way you fixed it doesn't make sense. If you use Begin/Process/End you need to use GenericList. If you don't use Begin/Process/End you can use it that way. It's mostly related whether you planned on using pipeline, as with current approach if you pass $ArrayOfObjects | YourFunction it will overwrite content as far as I read the code.
Ah I see you fixed the code. It's only in process now. So that should work.
As for OD/Hashtable I would say do it now? The difference is very minimal - you would need to check if (Object -is [Type]) or similar. But you also need to support non-pipeline. RIght now only pipeline is supported as far as I can see? Are you good with that change or do you need me to help you with that?
So, I wanted it to work with the pipeline. Otherwise the change is quite trivial like you mentioned. Do you think the pipeline method would not be used by many?
Right now both work; the pipeline as well as parameter input. But only PSObject is accepted. Probably I could add another parameter in a separate parameter set called InputHash or something, and handle it that way? Hold on. Lemme update the PR. See if that’s acceptable.
@PrzemyslawKlys I've made it this way now:
PSObject has been kept the default.
Hi, sorry for the late response. I've been busy with other modules. coming but to this one...
Do you think it would be possible to cover both cases with one variable and work in the pipeline? Because I guess this is an issue that you're talking about? Or is there something preventing that? I guess it would work, but I haven't played a lot with pipeline support.
If not, I'll be ok, with accepting this PR as is. Seems good to me :-)
Hi! Actually, I don't know why I didn't think of using the same variable and detecting the type. Let me look into it; I'll send an update to the PR. :)
Right, I remember now. I felt I would not be able to define its data type in the parameter section. Defining it as PSObject
and accepting Hashtable
input messed up the function. Not defining the datatype caused other issues. But I've tried a hack. Please let me know if this works. :smiley:
Oh, there seems to be an issue with the MacOS build. Something about the image not being available, I suppose.
What if we do this?
function ConvertTo-TeamsFact {
<#
.SYNOPSIS
Convert a PSCustomObject or a Hashtable to Teams facts.
.DESCRIPTION
Teams facts are name-value pairs. This module helps convert a PSObject or a Hashtable to Teams facts (only one level deep).
.PARAMETER InputObject
The Hashtable or PSObject that is output by another cmdlet.
.EXAMPLE
Get-ChildItem | Select-Object -First 1 | ConvertTo-TeamsFact
.EXAMPLE
@{ Product = 'Microsoft Teams'; Developer = 'Microsoft Corporation'; ReleaseYear = '2018' } | ConvertTo-TeamsFact
.NOTES
Ram Iyer (https://ramiyer.me)
#>
[CmdletBinding()]
param (
# The input object
[Array][Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
$InputObject
)
begin { }
process {
foreach ($Object in $InputObject) {
if ($Object -is [System.Collections.IDictionary]) {
$Facts = foreach ($Key in $Object.Keys) {
New-TeamsFact -Name $Key -Value $Object.$Key
}
} elseif ($Object -is [psobject]) {
$Facts = foreach ($Property in $Object.PsObject.Properties) {
New-TeamsFact -Name $Property.Name -Value $Property.Value
}
} else {
# elseif (($Object -is [int]) -or ($Object -is [long]) -or ($Object -is [string]) -or ($Object -is [char]) -or ($Object -is [bool]) -or ($Object -is [byte]) -or ($Object -is [double]) -or ($Object -is [decimal]) -or ($Object -is [single]) -or ($Object -is [array]) -or ($Object -is [xml])) {
Write-Error -Message 'The input is neither a PSObject nor a Hashtable. Operation aborted.' -Category InvalidData
break
}
$Facts
}
}
}
@{ Product = 'Microsoft Teams'; Developer = 'Microsoft Corporation'; ReleaseYear = '2018' } | ConvertTo-TeamsFact
[PSCustomObject] @{ Product = 'Microsoft Teams'; Developer = 'Microsoft Corporation'; ReleaseYear = '2018' }, @{ Product = 'Microsoft Teams'; Developer = 'Microsoft Corporation'; ReleaseYear = '2018' } | ConvertTo-TeamsFact
$Tests = @(
@{ Product = 'Microsoft Teams'; Developer = 'Microsoft Corporation'; ReleaseYear = '2018' }
[PSCustomObject] @{ Product = 'Microsoft Teams'; Developer = 'Microsoft Corporation'; ReleaseYear = '2018' }
)
ConvertTo-TeamsFact -InputObject $Tests
Actually instead of using hashtable you can use IDictionary which covers both $HashTable and OrderedDictionary.
And can you please add those tests as part of testing in pester? As for the Mac failure it's because the image was depreciated. I'll fix this.
The problem with adding a catch-all else block is that PowerShell implicitly converts the string into PSObject. That's why I had to place the [PSObject]
part in the end. I'll change hashtable
to System.Collection.IDictionary
, though.
Unfortunately I've never worked with Pester, but let me give it a shot. :)
Also, I feel it would be good if each record is presented as a section in the output. This way, the reader would know that each of these is a separate set of facts. Otherwise, something like this could happen:
Product: Microsoft Teams
Developer: Microsoft Corporation
Year: 2018
Product: Microsoft SQL Server
Developer: Microsoft Corporation
Year: 1992
etc.
Instead, if each of these comes as a separate section, it would be more distinguishable.
Product: Microsoft Teams
Developer: Microsoft Corporation
Year: 2018
Product: Microsoft SQL Server
Developer: Microsoft Corporation
Year: 1992
What do you think?
I'm not sure the Pester test I've written is good enough, but do let me know. This is my first ever. LOL.
Reverted the test. I'll try again.
Okay @PrzemyslawKlys, Pester tests are done as well. :)
Looks great! Have you noticed what I did in my example in regards to type [Array] and how I process InputObject with foreach? This should allow for non-pipeline use of array of hashtables?
Also for an additional module for testing if it's hashtable you can probably do $Variable.GetType() -Should ... don't think it's worth additional download.
Also, I feel it would be good if each record is presented as a section in the output. This way, the reader would know that each of these is a separate set of facts. Otherwise, something like this could happen:
Product: Microsoft Teams Developer: Microsoft Corporation Year: 2018 Product: Microsoft SQL Server Developer: Microsoft Corporation Year: 1992 etc.
Instead, if each of these comes as a separate section, it would be more distinguishable.
Product: Microsoft Teams Developer: Microsoft Corporation Year: 2018
Product: Microsoft SQL Server Developer: Microsoft Corporation Year: 1992
What do you think?
Not sure what you mean? Like giving people a choice between output facts only or facts divided by sections? or what is your question?
Also for an additional module for testing if it's hashtable you can probably do $Variable.GetType() -Should ... don't think it's worth additional download.
Sure, I thought we could compare the entire hash table to ensure that only those values are being generated. Otherwise sometimes when you pass a string, you get multiple other elements in the output hashtable. Anyway, I'd implemented the type check using the following line, now I've changed it to not use an additional module:
$Output | Should -BeOfType 'System.Collections.Specialized.OrderedDictionary'
Also, I feel it would be good if each record is presented as a section in the output. This way, the reader would know that each of these is a separate set of facts. Otherwise, something like this could happen: Product: Microsoft Teams Developer: Microsoft Corporation Year: 2018 Product: Microsoft SQL Server Developer: Microsoft Corporation Year: 1992 etc. Instead, if each of these comes as a separate section, it would be more distinguishable. Product: Microsoft Teams Developer: Microsoft Corporation Year: 2018 Product: Microsoft SQL Server Developer: Microsoft Corporation Year: 1992 What do you think?
Not sure what you mean? Like giving people a choice between output facts only or facts divided by sections? or what is your question?
I see. Hold on, I'll send an update.
Actually, there is a new issue. I was trying to say that the input should not be an array. If the input is an array, it could cause confusion to the person reading the message---there would be clutter. For instance, this kind of output could confuse:
name Name
value LICENSE
type fact
name LastWriteTime
value 03/05/2020 19:43:54
type fact
name Length
value 1068
type fact
name Name
value PSTeams.AzurePipelines.yml
type fact
name LastWriteTime
value 04/12/2020 10:38:30
type fact
name Length
value 1188
type fact
I feel each set should be in a separate section, like this (just as a matter of better UX):
name Name
value LICENSE
type fact
name LastWriteTime
value 03/05/2020 19:43:54
type fact
name Length
value 1068
type fact
<section break>
name Name
value PSTeams.AzurePipelines.yml
type fact
name LastWriteTime
value 04/12/2020 10:38:30
type fact
name Length
value 1188
type fact
But perhaps we could handle it later. For now, I've implemented your suggestion so that the behaviour is the same regardless of pipeline or inline input. Thank you!
But the output is not really going to be visible for the user? It's only visible if you output it to screen which is not supposed to happen.
If you mean what is displayed in Teams to separate one Hashtable from another hashtable separated with a section you probably need to create a separate cmdlet (or at least change the name) to something like ConvertTo-TeamsSectionFact or something with a switch "SeparateObjectsWithSections". I'm just guessing here - not sure if it makes sense ;-)
But the output is not really going to be visible for the user? It's only visible if you output it to screen which is not supposed to happen.
If you mean what is displayed in Teams to separate one Hashtable from another hashtable separated with a section you probably need to create a separate cmdlet (or at least change the name) to something like ConvertTo-TeamsSectionFact or something with a switch "SeparateObjectsWithSections". I'm just guessing here - not sure if it makes sense ;-)
Right, working on that Teams Section cmdlet. LOL.
If you could wait a bit before merging, I'll change this function into one that creates sections. :)
Sure, no hurry. I'm glad you are enjoying this and learning along the way.
So do you want me to merge this or you have an update coming?
Hi! Please give me a few hours; I'll send an update. Of course, that will be a separate cmdlet. Thought I'll discuss this; do we want to restrict the input for ConvertTo-TeamsFacts to a single object instead of an array of objects? Here's what I was thinking:
We have ConvertTo-TeamsFact as an internal function, that's not exposed. This function takes in only one object and converts it to Teams-like JSON.
We have a ConvertTo-TeamsSection as the exposed function, that accepts an array of objects, converts each to a set of facts, and creates a section for each object. Here's a before and after:
Name: John Doe
City: New York
Name: Jane Doe
City: Amsterdam
Name: Tom Roberts
City: London
Name: John Doe
City: New York
Name: Jane Doe
City: Amsterdam
Name: Tom Roberts
City: London
There, I've updated the PR with the new function. Added a slight enhancement as well, for better UX.
The PR Quality Review isn't passing for some reason, though; says, 'Error getting analysis'.
Didn't write a test because this function is just a wrapper.
I don't even check the PR Quality Review ;-) DOn't worry about that.
Weird, there seems to be an issue with the Pester
module. The only difference between the first and the second parameter sets in Invoke-Pester
is the parameter called OutputFile
; the parameter does not exist in the first set, while it is mandatory in the second. PowerShell is getting confused which one to pick and asks for the value for OutputFile
.
I ran the test script with an older version of Pester on my Linux and Windows machines, and it worked as expected. The version of Pester on my machines is 4.8.1. Pester 5.0.1 has this issue.
There is: https://evotec.xyz/converting-pester-v4-to-pester-v5-basics/
I need to fix this for all my projects :-)
5.0.1 was supposed to make it easier by adding compatibility, but i guess it failed a bit. I would still want it to use new syntax ;-)
Right. I think they need to change the DefaultParameterSet
.
For now do we set the OutputFile to some TEMP
directory? Anyway the containers will be purged, right?
https://twitter.com/nohwnd/status/1265921488022052870 - according to @nohwnd it's already deprecated while being added.
The changes are simple:
$result = Invoke-Pester -Script $PSScriptRoot\Tests -Verbose -EnableExit
with $result = Invoke-Pester -Path $PSScriptRoot\Tests #-Output Detailed
And if we use code outside of IT block we need to define it like:
foreach ($Key in $TestimoConfiguration['Forest'].Keys) {
$PSDefaultParameterValues = @{
"It:TestCases" = @{ Key = $Key; TestimoConfiguration = $TestimoConfiguration }
}
It -Name "Test Source $Key should not be NULL" {
$TestimoConfiguration['Forest'].$Key | Should -Not -Be $Null
}
It -Name "Test Source $Key should contain Enable" {
$TestimoConfiguration['Forest'].$Key.Keys | Should -Contain 'Enable'
}
It -Name "Test Source $Key should contain Source" {
$TestimoConfiguration['Forest'].$Key.Keys | Should -Contain 'Source'
}
}
}
Basically the variables defined outside of It block need to be passed via hashtable to It block.
I'm going through your blog post. :)
I also think I should start following the changes in Pester. Even though I don't use it at the moment, I do plan to. Should learn and keep updated.
I think Invoke-Pester is missing some parameter because it fails (as below) yet it shows success....
$result = Invoke-Pester -Path $PSScriptRoot\Tests #-Output Detailed
Yeah that does not enable to Exit on error, so you won't terminate. You should be able to just use the previous way of calling pester, if you are no 5.0.1. The parameter set is deprecated, but there is no alternative now that would be great. So just keep using it :)
@nohwnd but as you can see above @theramiyer tried and it was stuck with
Weird, there seems to be an issue with the
Pester
module. The only difference between the first and the second parameter sets inInvoke-Pester
is the parameter calledOutputFile
; the parameter does not exist in the first set, while it is mandatory in the second. PowerShell is getting confused which one to pick and asks for the value forOutputFile
.
So it would seem this needs fixing?
Gotcha, yeah that would be a bug, because the parameter used to be in a parameter set of it's own. It should not be mandatory. Please PR, or file a bug on Pester.
@nohwnd
This wasn't mandatory ;-) The question is if simple fix by removing mandatory parameter will be sufficient or all things go sideways.
PR it and we shall see. Can't really fix it right now, but I can release later today if someone fixes it.
Merged and fixed some code. Wanted to support both options.
Get-ChildItem | Select-Object -First 2 | ConvertTo-TeamsFact
ConvertTo-TeamsFact -InputObject (Get-ChildItem | Select-Object -First 2)
Sorry, it took so long to merge. Definitely my fault. Great job on the PR. Thank you again.
Thought it would help those who would like to convert a PSObject to a Teams fact. We had such a requirement at work, and I wrote this function for it.