Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams

Jethro posted:

Right, FISHMANPET was talking about ambiguity in terms of how Powershell interprets a statement versus the ways a person could interpret it.

Yes thank you, this is what I meant by ambiguity, explained better than I could probably do myself.

Adbot
ADBOT LOVES YOU

Irritated Goat
Mar 12, 2005

This post is pathetic.
Ok. This is more of a "ok, maybe I'm not going mad" question.

I've got an AD group that's just 3 letters. We'll call it FOO.

If I do Get-ADGroup -identity "FOO", I get an error it can't find the group. FOO is the name if I do it like Get-ADGroup -filter 'Name -like "FOO"'

:confused:

The Fool
Oct 16, 2003


Irritated Goat posted:

Ok. This is more of a "ok, maybe I'm not going mad" question.

I've got an AD group that's just 3 letters. We'll call it FOO.

If I do Get-ADGroup -identity "FOO", I get an error it can't find the group. FOO is the name if I do it like Get-ADGroup -filter 'Name -like "FOO"'

:confused:

Your groups 'Name' and 'SamAccountName' are different.

The identity argument for Get-AdGroup only takes: distinguished name, GUID, objectSid, or sAMAccountName

Toshimo
Aug 23, 2012

He's outta line...

But he's right!
So, every 3 weeks I have to change the passwords on a few hundred test accounts and afterwards, the services running as those accounts have to be changed as well.

I wrote a script to go out to the associated servers and change the passwords on the services and it 100% works as-advertised except... the services tend to revert to the old password after reboot.

Is there something I'm missing here to make this persistent?

code:
gwmi -NameSpace "root\CIMV2" -Class "Win32_Service" -ComputerName $Computer_Name -Filter "Name LIKE '0%Controller'" | % { $_stopservice() }
gwmi -NameSpace "root\CIMV2" -Class "Win32_Service" -ComputerName $Computer_Name -Filter "Name LIKE '0%Controller'" | % { $_.change($null,$null,$null,$null,$null,$null, "DOMAIN\$($_.Name -match '_(\d)_' | % { $Matches[1] })", $New_Password, $null, $null, $null ) }
gwmi -NameSpace "root\CIMV2" -Class "Win32_Service" -ComputerName $Computer_Name -Filter "Name LIKE '0%Controller'" | % { $_.startservice() }

Pile Of Garbage
May 28, 2007



What return value are you getting when the Change method of the Win32_Service WMI class is invoked? Going off the doco your usage looks OK however it's a bit vague as to whether the StartName parameter is always required. In the examples section it shows changing a password by specifying only the StartPassword parameter so maybe try it without the StartName parameter (e.g. $_.change($null,$null,$null,$null,$null,$null, $null, $New_Password, $null, $null, $null))?

Alternatively you may want to look at using the Set-Service cmdlet instead of WMI (Assuming your fleet has the required PS version).

Toshimo
Aug 23, 2012

He's outta line...

But he's right!

Pile Of Garbage posted:

What return value are you getting when the Change method of the Win32_Service WMI class is invoked? Going off the doco your usage looks OK however it's a bit vague as to whether the StartName parameter is always required. In the examples section it shows changing a password by specifying only the StartPassword parameter so maybe try it without the StartName parameter (e.g. $_.change($null,$null,$null,$null,$null,$null, $null, $New_Password, $null, $null, $null))?

Alternatively you may want to look at using the Set-Service cmdlet instead of WMI (Assuming your fleet has the required PS version).

I'll check what's getting returned when I get back to the office on Friday. I'm using wmi instead of Set-Service because they removed Set-Service's remote capability in v6 and later so you have to get funky with Invoke-Command to do that now.

sloshmonger
Mar 21, 2013

Toshimo posted:

So, every 3 weeks I have to change the passwords on a few hundred test accounts and afterwards, the services running as those accounts have to be changed as well.

I wrote a script to go out to the associated servers and change the passwords on the services and it 100% works as-advertised except... the services tend to revert to the old password after reboot.

Is there something I'm missing here to make this persistent?

code:
gwmi -NameSpace "root\CIMV2" -Class "Win32_Service" -ComputerName $Computer_Name -Filter "Name LIKE '0%Controller'" | % { $_stopservice() }
gwmi -NameSpace "root\CIMV2" -Class "Win32_Service" -ComputerName $Computer_Name -Filter "Name LIKE '0%Controller'" | % { $_.change($null,$null,$null,$null,$null,$null, "DOMAIN\$($_.Name -match '_(\d)_' | % { $Matches[1] })", $New_Password, $null, $null, $null ) }
gwmi -NameSpace "root\CIMV2" -Class "Win32_Service" -ComputerName $Computer_Name -Filter "Name LIKE '0%Controller'" | % { $_.startservice() }

Assuming that your AD environment will support it, this is exactly the case for a Managed Service Account, or Group Manage Service Account, was made for, unless you're also using those accounts to log in as or do other manual entries.

I'm not any help as to the issue you raised, though.

Toshimo
Aug 23, 2012

He's outta line...

But he's right!

sloshmonger posted:

Assuming that your AD environment will support it, this is exactly the case for a Managed Service Account, or Group Manage Service Account, was made for, unless you're also using those accounts to log in as or do other manual entries.

I'm not any help as to the issue you raised, though.

Yes, these are test accounts that are also logged into manually, and also used to run automated testing. So, they're simultaneously:
  1. AD accounts that can be logged into on any machine in the lab.
  2. The accounts these Jenkins services run as.
  3. The accounts they log into the mainframe as.
  4. The accounts the Selenium scripts run under.

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

Toshimo posted:

Yes, these are test accounts that are also logged into manually, and also used to run automated testing. So, they're simultaneously:
  1. AD accounts that can be logged into on any machine in the lab.
  2. The accounts these Jenkins services run as.
  3. The accounts they log into the mainframe as.
  4. The accounts the Selenium scripts run under.

Have you considered automating the provisioning and configuration of these machines with tools like packer and puppet? Manually rotating passwords is kind of an antiquated approach.

Toshimo
Aug 23, 2012

He's outta line...

But he's right!

New Yorp New Yorp posted:

Have you considered automating the provisioning and configuration of these machines with tools like packer and puppet? Manually rotating passwords is kind of an antiquated approach.

I don't get to make that call. The Agency mainframe requirement is password change every 28 days and we have to update AD to stay in sync. It would take a literal Act of Congress to change that, I suspect.

Pile Of Garbage
May 28, 2007



That you're changing service account passwords at all is a marked improvement over the usual practice of ticking "Password never expires" and calling it a day (Which is what I've seen in almost every single AD environment I've encountered).

kiwid
Sep 30, 2013

I'm having a hard time doing what seems like a simple task. Can someone help me out here?

All I'm trying to do is get a CSV file with the following fields:

code:
GroupName | GroupCategory | GroupEmailAddress | CommaSeparatedMemberNames
Example:

code:
Branch London Sales | Security | [email]branchlondonsales@example.com[/email] | John, Dave, Sarah, ...
Branch Chicago Sales | Distribution | [email]branchchicagosales@example.com[/email] | Mike, Amy, ...
Branch New York Sales | Security | [email]branchnewyorksales@example.com[/email] | Matt, Debbie, ...
Some things I've tried which are obviously not working:

code:
Get-ADGroup -Filter * -Properties * | Get-ADGroupMember | Select-Object Name, GroupCategory, mail
code:
$report = @()

$groups = Get-ADGroup -Filter * -Properties *

ForEach ($group in $groups) {

    $members = Get-ADGroupMember -Identity $group

    $members | Add-Member -Name "GroupName" -Value $group.Name -MemberType NoteProperty -Force
    $members | Add-Member -Name "GroupCategory" -Value $group.GroupCategory -MemberType NoteProperty -Force
    $members | Add-Member -Name "GroupMail" -Value $group.mail -MemberType NoteProperty -Force

    $report += $members
}

 
$report | Export-Csv "C:\Temp\export.csv"

kiwid fucked around with this message at 21:09 on Dec 3, 2019

The Fool
Oct 16, 2003


You probably want to try to use a calculated property for the group members.

I made this atrocity real quick
code:
get-adgroup -Filter * -SearchBase "dc=contoso,dc=com" -Properties Members, mail | Select -Property Name, GroupCategory, mail, @{Name = 'Members'; Expression = {$memberList = ""; Foreach ($Member in $_.Members) {$memberList += $Member.Split(",")[0].Split("=")[1] + ","} $memberList}} | Export-CSV "Desktop\test.csv" -NoTypeInformation
This is the calculated property and is an argument of my select cmdlet.

code:
@{Name = 'Members'; Expression = {$memberList = ""; Foreach ($Member in $_.Members) {$memberList += $Member.Split(",")[0].Split("=")[1] + ","} $memberList}}
The expression enumerates the members property from the ADGroup object, splitting up the DN in order to extract the CN, then concatenating the results with a "," seperator. Ultimately returning the resulting string.
code:
$memberList = ""; 
Foreach ($Member in $_.Members) {
    $memberList += $Member.Split(",")[0].Split("=")[1] + ","
} 
$memberList

The Fool fucked around with this message at 21:30 on Dec 3, 2019

kiwid
Sep 30, 2013

The Fool posted:

You probably want to try to use a calculated property for the group members.

I made this atrocity real quick
code:
get-adgroup -Filter * -SearchBase "dc=contoso,dc=com" -Properties Members, mail | Select -Property Name, GroupCategory, mail, @{Name = 'Members'; Expression = {$memberList = ""; Foreach ($Member in $_.Members) {$memberList += $Member.Split(",")[0].Split("=")[1] + ","} $memberList}} | Export-CSV "Desktop\test.csv" -NoTypeInformation
This is the calculated property and is an argument of my select cmdlet.

code:
@{Name = 'Members'; Expression = {$memberList = ""; Foreach ($Member in $_.Members) {$memberList += $Member.Split(",")[0].Split("=")[1] + ","} $memberList}}
The expression enumerates the members property from the ADGroup object, splitting up the DN in order to extract the CN, then concatenating the results with a "," seperator. Ultimately returning the resulting string.
code:
$memberList = ""; 
Foreach ($Member in $_.Members) {
    $memberList += $Member.Split(",")[0].Split("=")[1] + ","
} 
$memberList

Hell ya, this worked. Thanks.

mllaneza
Apr 28, 2007

Veteran, Bermuda Triangle Expeditionary Force, 1993-1952




mystes posted:

Yeah you don't need to set a passphrase for the key. All it does is encrypt the private key file in case it gets stolen from your computer somehow.

If you just want to be able to log in from one computer to a bunch of other computers, you just have to append the contents of the id_(whatever).pub file from the one computer to the authorized_keys file on each of the computers you want to be able to connect to.

(Openssh on other platforms has a script just for this called "ssh-copy-id" but you don't need to use that.)

This is crossing into territory where you're better off setting up Ansible instead of rolling your own. Two prime reasons are, Ansible is a resume keyword that's good to have, and it has a password vault that you can access from the command line or a script/playbook.

One of the things I like about Ansible is that it's a central configuration store for how you do things to remote systems. All the clients need is a consistent way to run remote commands.

If only we had that in our environment.

mystes
May 31, 2006

mllaneza posted:

This is crossing into territory where you're better off setting up Ansible instead of rolling your own. Two prime reasons are, Ansible is a resume keyword that's good to have, and it has a password vault that you can access from the command line or a script/playbook.

One of the things I like about Ansible is that it's a central configuration store for how you do things to remote systems. All the clients need is a consistent way to run remote commands.

If only we had that in our environment.
Are you saying that using ansible to store ssh passwords is better than using public key authentication (and calling public key authentication "rolling your own") or are you saying ansible should be used to copy the public key to the remote computers?

The Fool
Oct 16, 2003


The latter, I would hope

mystes
May 31, 2006

I wasn't even clear on whether Toast Museum currently had a way of sshing in to the servers at all, but I guess if they were set up for passphrase authentication and you really wanted to you could put all the passphrases in ansible, use it to copy the id file to each server and then ideally disable passphrase authentication?

Toast Museum
Dec 3, 2005

30% Iron Chef
The issue wasn't making SSH connections per se, but rather making it usable for one-to-many operations by avoiding per-device passphrase entry and other confirmation/approval prompts. I'm still stuck doing monolithic imaging for macOS, so adding a couple public keys there was no problem, and disabling strict host key checking got it the rest of the way. I know that's a security risk, but the way I've been forced to run things in general involves so many big dumb security risks that this hardly budges the needle.

I remember looking briefly at Ansible some time ago and coming away with the impression that it's intended for infrastructure deployment, whereas I'm dealing with a bunch of workstations. I'd be using Active Directory and Jamf right now if I weren't being prevented from doing so by departmental turf-war nonsense. Have I got the wrong idea of what Ansible is for?

The Fool
Oct 16, 2003


No you don’t, but it is a flexible enough platform that if you have a consistent way to execute remote commands in your environment you can absolutely push configuration changes to workstations as well.

The Fool
Oct 16, 2003


I'm building a PowerShell wrapper for an API and am thinking about releasing it open source as a module. Does anyone have any links to best practices/standards for doing this so I don't embarrass myself?

Judge Schnoopy
Nov 2, 2005

dont even TRY it, pal

The Fool posted:

I'm building a PowerShell wrapper for an API and am thinking about releasing it open source as a module. Does anyone have any links to best practices/standards for doing this so I don't embarrass myself?

I'd be interested in looking at it once you have it up. I've done a bunch of these API wrappers in powershell, they can be really cool.

For best practices, my company has a module standard we use that I've never had to think twice about. At the basics you'll have your psd1, psm1, and readme files in the root. Then your private functions folder and public functions folder. We use one file per function which is nice, and the psm1 links it all together.

mllaneza
Apr 28, 2007

Veteran, Bermuda Triangle Expeditionary Force, 1993-1952




mystes posted:

I wasn't even clear on whether Toast Museum currently had a way of sshing in to the servers at all, but I guess if they were set up for passphrase authentication and you really wanted to you could put all the passphrases in ansible, use it to copy the id file to each server and then ideally disable passphrase authentication?

That would be a very good first use of a one-to-many system !

I've got 2600-ish desktops in my part of the environment. I ran my Check-Hosts script against every system on the list. My hit rate for the PowerShell Remoting test was 30. I have Work To Do.

Check-Hosts takes a text file full of hostnames, a folder, and admin credentials (Get-Credential) to save reports in. It starts with a DNS lookup for each one, saving hostnames into DNS-Yes.txt and DNS-No.txt. It then takes DNS-Yes.txt and runs Test-Connection on all of those hosts and saves out Pingable.txt and Not-Pingable.txt.

The pingable machines are then tested with Get-Service -computername and Enter-PSSession, saving results as before, plus I'm specifically catching authentication errors and saving those off into an extra file. I'm planning to add a psexec test to write something simple to a unique text file, and a Test-Path to see if it got created.

These tests helped us get the number of machines we could remotely push Acronis and KACE to from 45% of a random sample to 85%. Now I don't have to rely on hand-rolled tools to manage my network !

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams

The Fool posted:

I'm building a PowerShell wrapper for an API and am thinking about releasing it open source as a module. Does anyone have any links to best practices/standards for doing this so I don't embarrass myself?

I've tried to find things but haven't had much luck. We've written a number of modules like that and released them as open source: https://github.com/umn-microsoft-automation.
UMN-ActiveDirectory is one that I think we've organized the files the best and used AzureDevops for testing and publishing. UMN-Google and UMN-VMWareRA are used a ton in our production workloads, but I'm really not happy with how they do error handling (they don't do any at all) so I'm not sure I'd model any code you write after that. But like I said I haven't found good examples of big modules that are just REST API wrappers. Most of the big wrapper modules I've seen are wrapping .net Libraries rather than using the REST API directly.

Toast Museum
Dec 3, 2005

30% Iron Chef
Rough timeline of a REST API wrapper I've started working on:

  1. Script a couple proof-of-concept cmdlets
  2. Decide that a compiled module would have some benefits over a script module
  3. Look into how interacting with a REST API works in C#
  4. :shepicide:

The jump in complexity between Invoke-RestMethod in PowerShell and System.Net.HttpClient in C# is loving steep.

mystes
May 31, 2006

Toast Museum posted:

Rough timeline of a REST API wrapper I've started working on:

  1. Script a couple proof-of-concept cmdlets
  2. Decide that a compiled module would have some benefits over a script module
  3. Look into how interacting with a REST API works in C#
  4. :shepicide:

The jump in complexity between Invoke-RestMethod in PowerShell and System.Net.HttpClient in C# is loving steep.
It's really not that bad, it's just very verbose because c# doesn't have a built in helper method that does json serialization/deserialization for you. A lot of the .net api is pretty crufty, but they've been improving it slowly, and maybe now that they added a new json library to .net core 3.0 they'll eventually create helper methods for stuff like this since they're so commonly needed now.

The Fool
Oct 16, 2003


You mean I don't have to add newtonsoft to every project now?

mystes
May 31, 2006

The Fool posted:

You mean I don't have to add newtonsoft to every project now?
Not if you're on .net core 3.0 or higher. You can use System.Text.Json.

Toast Museum
Dec 3, 2005

30% Iron Chef

mystes posted:

It's really not that bad, it's just very verbose because c# doesn't have a built in helper method that does json serialization/deserialization for you. A lot of the .net api is pretty crufty, but they've been improving it slowly, and maybe now that they added a new json library to .net core 3.0 they'll eventually create helper methods for stuff like this since they're so commonly needed now.

No, that's fair, it does seem like mostly a verbosity issue. I wasn't even thinking of json, just the comparative fiddliness of instantiating a client, plus a request object to give the client, plus a response object to contain the results. Sounds straightforward enough when I put it that way, but I haven't messed with it enough yet for the details to fully sink in, so for now I definitely miss letting a cmdlet or two do all the work.

On the upside, I'm just making this thing to avoid repetitive tasks and learn some coding in the process, so the only person sweating me about it is me.

Pile Of Garbage
May 28, 2007



Toast Museum posted:

No, that's fair, it does seem like mostly a verbosity issue. I wasn't even thinking of json, just the comparative fiddliness of instantiating a client, plus a request object to give the client, plus a response object to contain the results. Sounds straightforward enough when I put it that way, but I haven't messed with it enough yet for the details to fully sink in, so for now I definitely miss letting a cmdlet or two do all the work.

On the upside, I'm just making this thing to avoid repetitive tasks and learn some coding in the process, so the only person sweating me about it is me.

It's good stuff to learn because not only is understanding HTTP and REST API fundamentals useful but the way System.Net.HttpClient works is quite similar to equivalent libraries in other languages (e.g. requests in Python).

Agrikk
Oct 17, 2003

Take care with that! We have not fully ascertained its function, and the ticking is accelerating.
How do I go about scripting some DNS and AD cleanup for servers that no longer exist?


I have a trio of AWS autoscaling groups that launch and terminate Windows 2016 servers running IIS and as part of launch, the servers connect to a DMZ AD. When they get terminated by the ASG, they leave behind a valid AD computer object and a record in DNS and I want to clean those up on a routine basis.

Can anyone point me in the right direction?

The Fool
Oct 16, 2003


https://docs.microsoft.com/en-us/powershell/module/addsadministration/?view=win10-ps

The ActiveDirectory module, which is a part of RSAT but can be extracted and deployed independentally.

Use it to manage your AD objects, and if DNS and AD are set up properly, the DNS entries should be removed automatically based on your scavenging settings.

xsf421
Feb 17, 2011

Agrikk posted:

How do I go about scripting some DNS and AD cleanup for servers that no longer exist?


I have a trio of AWS autoscaling groups that launch and terminate Windows 2016 servers running IIS and as part of launch, the servers connect to a DMZ AD. When they get terminated by the ASG, they leave behind a valid AD computer object and a record in DNS and I want to clean those up on a routine basis.

Can anyone point me in the right direction?

Lifecycle termination hook lambda running a powershell core script to remove themselves from AD?

Toast Museum
Dec 3, 2005

30% Iron Chef
When defining a function, is it possible to iterate over the parameters contained in a particular parameter set? I'm aiming for something like this:
code:
Function Do-Thing
{
    Param
    (
        #Parameters here
    )
    begin
    {
        foreach ($Param in $PSBoundParameters)
        {
            if ($Param -in <SomeParameterSet>)
            {
                $SomeDictionary.Add($Param)
            }
        }
    }
}
I'm writing a function to invoke a search function in a REST API. There's a bunch of different search filters available, and my plan is to give each filter a parameter in the PowerShell function, all of them within a parameter set containing only these filters, and use the bound parameters within that set to populate a collection to convert to JSON and send to the API function.

The Fool
Oct 16, 2003


I've never done it but you should be able to accomplish what you want with either Dynamic Parameters or ValueFromRemainingArguments

Both are described here: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters?view=powershell-5.1

Chrungka
Jan 27, 2015

Toast Museum posted:

When defining a function, is it possible to iterate over the parameters contained in a particular parameter set? I'm aiming for something like this:
code:
Function Do-Thing
{
    Param
    (
        #Parameters here
    )
    begin
    {
        foreach ($Param in $PSBoundParameters)
        {
            if ($Param -in <SomeParameterSet>)
            {
                $SomeDictionary.Add($Param)
            }
        }
    }
}
I'm writing a function to invoke a search function in a REST API. There's a bunch of different search filters available, and my plan is to give each filter a parameter in the PowerShell function, all of them within a parameter set containing only these filters, and use the bound parameters within that set to populate a collection to convert to JSON and send to the API function.

$PSBoundParameters is dictionary. So you can either iterate over $PSBoundParameters.Keys and do something like this
code:
foreach ($Key in $PSBoundParameters.Keys)
{
  if ($Key -in <SomeParameterSet>)
  {
    $SomeDictionary.Add($key, $PSBoundParameters[$Key])
  }
}
or do
code:
foreach ($Key in <SomeParameterSet>)
{
  if ($PSBoundParameters.ContainsKey($Key))
  {
    $SomeDictionary.Add($key, $PSBoundParameters[$Key])
  }
}
I hope that's what are you asking for.

edit: if I you are asking about actual parameter set (as defined on parameters of your function), you can get those by
code:
$cmd = $PSCmdlet.MyInvocation.MyCommand
$parameterNamesInSomeParameterSet = $cmd.ParameterSets | where -Property Name -eq <SomeParameterSet> | select -ExpandProperty Parameters | select -ExpandProperty Name
edit 2: found a way how to get to current command info from within command invocation

Chrungka fucked around with this message at 01:40 on Dec 14, 2019

Judge Schnoopy
Nov 2, 2005

dont even TRY it, pal

Toast Museum posted:

When defining a function, is it possible to iterate over the parameters contained in a particular parameter set? I'm aiming for something like this:
code:
Function Do-Thing
{
    Param
    (
        #Parameters here
    )
    begin
    {
        foreach ($Param in $PSBoundParameters)
        {
            if ($Param -in <SomeParameterSet>)
            {
                $SomeDictionary.Add($Param)
            }
        }
    }
}
I'm writing a function to invoke a search function in a REST API. There's a bunch of different search filters available, and my plan is to give each filter a parameter in the PowerShell function, all of them within a parameter set containing only these filters, and use the bound parameters within that set to populate a collection to convert to JSON and send to the API function.

Based on your code snippet, why not just set a single parameter as an array and pass your search values in as a single object? That way you can dynamically generate your search filter array with another command and pipe it in to this command if you wanted to.

E; nevermind I see you're adding to a dictionary. Can you just accept a hashtable as your param type then?

Judge Schnoopy fucked around with this message at 02:08 on Dec 14, 2019

Toast Museum
Dec 3, 2005

30% Iron Chef

Chrungka posted:

edit: if I you are asking about actual parameter set (as defined on parameters of your function), you can get those by
code:
$cmd = $PSCmdlet.MyInvocation.MyCommand
$parameterNamesInSomeParameterSet = $cmd.ParameterSets | where -Property Name -eq <SomeParameterSet> | select -ExpandProperty Parameters | select -ExpandProperty Name
edit 2: found a way how to get to current command info from within command invocation

Yep, that's what I meant! God, the default formatting on these types is fucky; I'd actually poked around in $MyInvocation.MyCommand before posting and just hadn't managed to untangle the knot :doh:

As written, that snippet does return the parameters for that parameter set, but it also returns parameters from __AllParameterSets, which need to be excluded. Happily, it turns out that the parameter sets to which a parameter has been explicitly assigned are identified in $MyInvocation.MyCommand.ParameterSets.Parameters.Attributes.ParameterSetName, an intuitive place to look if ever I've seen one.

Since $PSBoundParameters contains both the name and value of each parameter we're interested in (plus some we don't want), starting there and filtering out the pairs we don't need seemed more straightforward than starting from the parameter set. It does still seem more straightforward, but there is a wrinkle: enumerating the keys to use them in Where-Object means that we're dealing with an array of KeyValuePair objects rather than a dictionary. The KeyValuePairs can't be cast to a new dictionary (and ConvertTo-Json doesn't parse them correctly), so the dictionary has to be initialized first, then added to with a foreach.

Putting it all together, here's the reason I'm not currently binge-watching the new season of The Expanse:
code:
$SomeParameterSet = 
    $MyInvocation.MyCommand.ParameterSets.Parameters |
    Where-object {$_.Attributes.ParameterSetName -eq "SomeParameterSet"} |
    Select-Object -ExpandProperty "Name"

$SomeParameterSetHashtable = @{}
$PSBoundParameters.GetEnumerator() |
    Where-Object {$_.Key -in $SomeParameterSet} |
    ForEach-Object {$SomeParameterSetHashtable.Add($_.Key,$_.Value)}

$SearchFilterSet = $SomeParameterSetHashtable | ConvertTo-Json
Invoke-RestMethod @Splat -Body $SearchFilterSet
:shepicide:

Judge Schnoopy posted:

Based on your code snippet, why not just set a single parameter as an array and pass your search values in as a single object? That way you can dynamically generate your search filter array with another command and pipe it in to this command if you wanted to.

E; nevermind I see you're adding to a dictionary. Can you just accept a hashtable as your param type then?

I do intend to allow passing a hashtable to the function, but I don't care for it as the primary/only way to use it. Giving each of the API's search filters its own parameter within the function means that I can strongly type the parameters and leverage the parameter validation attributes, which seems like less of a pain than doing all the validation from scratch.

Pile Of Garbage
May 28, 2007



So is the idea to have a function which accepts an arbitrary number of unnamed parameters? Unless I misread things that sounds like a bad idea that will very quickly become unsupportable unless it's intended on being a private function called by more accessible functions in the module.

Adbot
ADBOT LOVES YOU

Toast Museum
Dec 3, 2005

30% Iron Chef

Pile Of Garbage posted:

So is the idea to have a function which accepts an arbitrary number of unnamed parameters? Unless I misread things that sounds like a bad idea that will very quickly become unsupportable unless it's intended on being a private function called by more accessible functions in the module.

Not at all. The parameters are all named and defined within the function. They're all strongly typed and, where applicable, have an enum or validation attribute to make sure they only accept the right sorts of values. From an end-user perspective, they're completely normal parameters.

Most of the parameters, but not all of them, contribute to a hashtable that gets converted to a JSON object and passed to a REST API. All of the parameters that go into this hashtable belong to one particular parameter set. What I was looking for (and eventually found) was a programmatic way to say "take all of the bound parameters that belong to this one parameter set and do this with them."

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply