Help with Foreach loop, and saving results to an PSCustomObject

I have a secret ID that I use to retrieve a password and save the results in PSCustomObject.
My question is, how do I turn this into a foreach loop so I can retrieve multiple secret ID’s?
The next question is how do I add to the custom object.

#secretID
$secretId = 2971 

#WebRequest
$url = "https://pwmgr.famoso.com/winauthwebservices/WebService.asmx"
$proxy = New-WebServiceProxy -uri $url -UseDefaultCredential -Namespace "ss"

#Get UserName and Password
$UserName = ($proxy.GetSecret($secretId, $true, $null).Secret).Items.Value[3]
$PassWord = ($proxy.GetSecret($secretId, $false, $null).Secret).Items.Value[4]

#Save results in custom object names $Secret
$Secret = [PSCustomObject]@{
    Username = "$UserName"
    Password = "$PassWord"}
  

#Display the custom object 
$Secret

Username Password      
-------- --------      
UserName n@pf3iqq5c%Rj8

I tried the foreach loop below and it appears to add all 4 secrets I called to the custom object. But when I do a $secret | gm it only returns the last item in the array

$SecretIdArray = "3013","3014","3015","3016"
foreach ($SecretID in $SecretIdArray)
{

#WebRequest
$url = "https://pwmgr.famoso.com/winauthwebservices/WebService.asmx"
$proxy = New-WebServiceProxy -uri $url -UseDefaultCredential -Namespace "pwmgr"

#Get UserName and Password
$UserName = ($proxy.GetSecret($secretId, $true, $null).Secret).Items.Value[3]
$PassWord = ($proxy.GetSecret($secretId, $false, $null).Secret).Items.Value[4]

#Save results in custom object names $Secret
$Secret = [PSCustomObject]@{
    Username = "$UserName"
    Password = "$PassWord"}
  
$Secret

}

Username  Password      
--------  --------      
UserName  n@pf3iqq5c%Rj8
UserName1 n@pf3iqq5c%Rj8
UserName2 n@pf3iqq5c%Rj8
UserName3 n@pf3iqq5c%Rj8

$Secret | gm


   TypeName: System.Management.Automation.PSCustomObject

Name        MemberType   Definition                    
----        ----------   ----------                    
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()             
GetType     Method       type GetType()                
ToString    Method       string ToString()             
Password    NoteProperty string Password=n@pf3iqq5c%Rj8
Username    NoteProperty string Username=UserName3

Further more if I do variable.property I can only see SecretID 3016

$Secret.Username
UserName3

I would like to be able to do #Secret.Username and return all usernames.

Any assistance would be greatly appreciated.

If you assign a value to a variable inside a loop you overwrite the value with each loop irteration. And you end up with the last value in your variable. :wink:

Remove the variable assignment for the [PSCustomObject] inside the loop and capture the output of the foreach loop in a variable.

You are misunderstanding the purpose of Get-Member. Its purpose is not to return all the data in an object, rather it returns information about the members, properties and methods found in an object. It returns only the first example of any one of those it finds so you know they exist in the object.

Even once you resolve having an object containing all your secret IDs, Get-Member will only return information about the first noteproperty it finds simply to let you know that the object contains one or more noteproperty.

Why $Secret is only holding 1 ID at the end of the script run is because you have not told your script to retain all the data it finds. Currently all found data is output to the console while the foreach loop is running, but once the loop finishes nothing was written into your script retain that data.

If you make the following additions, you can keep the data for use later in your script

#variable to hold results
$results = @()

foreach ($SecretID in $SecretIdArray)
{

#WebRequest
$url = "https://pwmgr.famoso.com/winauthwebservices/WebService.asmx"
$proxy = New-WebServiceProxy -uri $url -UseDefaultCredential -Namespace "pwmgr"

#Get UserName and Password
$UserName = ($proxy.GetSecret($secretId, $true, $null).Secret).Items.Value[3]
$PassWord = ($proxy.GetSecret($secretId, $false, $null).Secret).Items.Value[4]

#Save results in custom object names $Secret
$Secret = [PSCustomObject]@{
    Username = "$UserName"
    Password = "$PassWord"}

#change to
$results += $Secret

}  

# now you can check that the variable results actually holds what you want it to

$results

#check that all user names are there
$results.Username

# send to a csv file

$Results | Export-Csv -Path <#whatever path you want #> -NoTypeInformation

Instead of …

I’d recommend something like this:

$results = 
foreach ($SecretID in $SecretIdArray)
{
 ......
[PSCustomObject]@{
    Username = "$UserName"
    Password = "$PassWord"}
}  
1 Like

show off :stuck_out_tongue:

So, which should someone use? Just my opinion depending on where you are in your journey of learning PS.

My example helps make clear that each time the loop runs, the value of Secret is overridden and if you want to save each return it has to go somewhere. Good for learning.

Olaf’s example if you understand or once you understand loops, is cleaner. Once we understand, we should strive to write clean, but self-documenting code as much as possible.

The example you show is a very old and bad practice. I’m sure almost all new users eventually run into this pattern causing extreme slowness due to how it works behind the scenes. If you aren’t teaching how to collect easily using implicit output, letting powershell do the hard work, and must teach explicit collection, please use a generic list. I wish I could scrub all the examples using this method from the internet… but sadly even new samples all over are riddled with this. I really wish the powershell team would’ve just made this array a list/Arraylist under the hood somehow.

1 Like

Another reason/feature that I like about Lists is that it is much easier to add/remove items than an array defined as @() using the Add and Remove methods.

Back before I knew better, I was using += many times in a very large script and eventually learned that each time you add, the Array is cleared and recreated to add an additional item. One can easily see how inefficient this is.

My $.02

1 Like

Thanks, in my particular example this returned an error.

Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named ‘op_Addition’.

Then you did something wrong. :man_shrugging:t3: If you like - share the code you used and we might be able to fix it. :wink:

Thanks for introducing a new topic to me @krzydoug. I used the command below to make a generic list of my initial secret id array. Once I created the empty object ($Secret) with the type generic list. I was able to use $Secret.Add.

Also thanks to @Matt for showing I needed to use $secret = @() to hold the results and explaining what i was missing…

[System.Collections.Generic.List[string]]$SecretIdArray = @(3013, 3014, 3015, 3016)
$secret = New-Object 'System.Collections.Generic.List[PSObject]'

#WebRequest
$url = "https://pwmgr.famoso.com/WebService.asmx"      
$proxy = New-WebServiceProxy -uri $url -UseDefaultCredential -Namespace "pwmgr"


$SecretID = @()
  foreach ($SecretID in $SecretIdArray)
{


#Get UserName and Password
$UserName = ($proxy.GetSecret($secretId, $true, $null).Secret).Items.Value[3]
$PassWord = ($proxy.GetSecret($secretId, $false, $null).Secret).Items.Value[4]

#Save results in custom object names $Secret
$Secret.Add([PSCustomObject]@{
    Username = "$UserName"
    Password = "$PassWord"})

}  

# now you can check that the variable results actually holds what you want it to

$secret

Username  Password      
--------  --------      
UserName  n@pf3iqq5c%Rj8
UserName1 ZMJf5nzfeF*y)Q
UserName2 %tYEFc4%4UzJKC
UserName3 Z76Dk%$L9HmGf!

You are overcomplicating this. :wink:

$SecretIdArray = @(3013, 3014, 3015, 3016)

$url = 'https://pwmgr.famoso.com/WebService.asmx'
$proxy = New-WebServiceProxy -uri $url -UseDefaultCredential -Namespace 'pwmgr'

$SecretIDList = 
foreach ($SecretID in $SecretIdArray) {

    [PSCustomObject]@{
        Username = ($proxy.GetSecret($secretId, $true, $null).Secret).Items.Value[3]
        Password = ($proxy.GetSecret($secretId, $false, $null).Secret).Items.Value[4]
    }
}  

$SecretIDList
1 Like