ForEach Help

This topic contains 14 replies, has 3 voices, and was last updated by  Chris 5 months, 3 weeks ago.

  • Author
    Posts
  • #71872

    Chris
    Participant

    Hi Guys,

    I'm working on capturing a csv file via windows forms, i then want to perform a ForEach loop on the objects inside,
    I can get it to work when i just specify "import-csv". But for some reason i just can't figure out how to pass a variable to the ForEach loop.

    Code below to demonstrate:

     
    Function Get-Filename($initialDirectory)
    {
        [System.Reflection.Assembly]::LoadWithPartialName("System.windows.foms") | Out-Null
    
        $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
        $OpenFileDialog.InitialDirectory = $initialDirectory
        $OpenFileDialog.filter = "CSV (*.csv)| *.csv"
        $OpenFileDialog.ShowDialog() | Out-Null
        $OpenFileDialog.FileName
    }
    
    Get-Filename -initialDirectory "C:"
    
    Import-Module ActiveDirectory
    
    $credpath = 'C:\safe\secretfile.txt'
    $c = Import-Clixml -Path $credpath
    
    # Set the new password
    $newPassword = ConvertTo-SecureString -AsPlainText "dummy2017" -Force
    
     ##### HERE IS WHERE I NEED TO PASS VARIABLE? ###### ForEach-Object {
     $samAccountName = $_."samAccountName"
     
    # Reset user password.
    Set-ADAccountPassword -Identity $samAccountName -NewPassword $newPassword -Reset -credential $c
     
    # Force user to reset password at next logon.
    # Remove this line if not needed for you
    Write-Host " AD Password has been reset for: "$samAccountName
    } 

    I know my code may be laid out in a weird order, but im trying to piece 2 separate scripts that i have.

  • #71875

    Don Jones
    Keymaster

    What is it that you're trying to enumerate in the ForEach-Object loop?

    • #71876

      Chris
      Participant

      Hi don,

      I'm grabbing the samAccountName field in the csv and looping through, resetting the passwords of the accounts it finds.

      It works flawlessly when i take the windows forms element out and just import the csv. But i wanted to make it a bit more, friendly for helpdesk.

      For info, this is my error:

      Get-Content : Cannot bind argument to parameter 'Path' because it is null.
      At line:23 char:14
      +  Get-Content $OpenFileDialog.FileName | ForEach-Object {
      +              ~~~~~~~~~~~~~~~~~~~~~~~~
          + CategoryInfo          : InvalidData: (:) [Get-Content], ParameterBindingValidationException
          + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.GetContentComman 
         d
  • #71878

    Don Jones
    Keymaster

    I don't see where you're importing a CSV. I see an Import-CliXML...?

  • #71879

    Stephen Valdinger
    Participant

    I've seen that Get-Filename function in the wild before. Basically you're not storing your CSV data in a variable. I'm going to make the assumption that your CSV file is just full of SamAccountNames. If there are more headers in the file than that, and SamAccountName is the column of data you are after, change $item to $item.SamAccountname in the code below.

    Change this:

    Get-Filename -initialDirectory "C:"

    To this:

    $csvItems = Get-Filename -initialDirectory "C:"

    Also, that code makes me shutter. Organize it like this:

    Import-Module ActiveDirectory
    
    #Variable declaration for credentials
    $credpath = 'C:\safe\secretfile.txt'
    $c = Import-Clixml -Path $credpath
    
    Function Get-Filename($initialDirectory)
    {
        [System.Reflection.Assembly]::LoadWithPartialName("System.windows.foms") | Out-Null
    
        $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
        $OpenFileDialog.InitialDirectory = $initialDirectory
        $OpenFileDialog.filter = "CSV (*.csv)| *.csv"
        $OpenFileDialog.ShowDialog() | Out-Null
        $OpenFileDialog.FileName
    }
    
    #Create a variable to store the data from your CSV file into.
    $csvItems = Get-Filename -initialDirectory "C:"
    
    #You'd pass your variables here, inside a ForEach Loop. Since this is a script that's doing a couple of things,
    #it would be much easier to loop this way rather than piping to ForEach-Object.
    
    Foreach($item in $csvItems){
    
        # Set the new password
        $newPassword = ConvertTo-SecureString -AsPlainText "dummy2017" -Force
    
        # Reset user password. Remove the -Reset parameter if you don't want the user to change their password at next Logon.
        Set-ADAccountPassword -Identity $item -NewPassword $newPassword -Reset -credential $c
     
        #Stop using Write-Host. Every time you do a child bleeds from the eyes.
        Write-Output " AD Password has been reset for: $item" 
    
    } 
    
    • #71882

      Chris
      Participant

      Hi Stephen,

      Ha i know, it was quickly thrown together, but neat code prevents mistakes! I will give that a try and fingers crossed, yes there is only one header, as i'm handling the password 'in-script' So they don't get to decide it. This is all in a test phase anyway.

      Don – Basically i'm getting them to select the CSV file via windows forms which will be a universal format (presumption). I then wanted to grab the accounts under samAccountName once the file has been selected and stored into a variable.

      Thanks guys

  • #71887

    Don Jones
    Keymaster

    Ok. Well, I don't think your code is going to lead you down a good life path, but here's the basic model:

    $content = Import-CSV filename.csv
    ForEach ($item in $content) {
    
    }
    

    Within the ForEach loop (I'm deliberately not using ForEach-Object, notice), the $item variable will represent one line of your CSV. If your CSV has a "samAccountName" column header, then $item.samAccountName would reference that.

    • #71891

      Chris
      Participant

      Hi Don,

      Thank you, as i mentioned it worked well with a foreach-object loop, when using import-csv. But what i'm trying to achieve here is a user selecting the file, via a windows form. The trouble i was having, is getting the contents of the file that the user selected, into a variable for the loop to go through. I'm still a new Powershell user, so i'm learning as i go.

      Stephen – Unfortunately this didn't work, here's the result:

      Set-ADAccountPassword : Cannot find an object with identity: 'C:\resetpasswordbulk.csv' under: 'DC=***,DC=***'.
      At N:\PSScripts\BulkResetGUI.ps1:28 char:1
      + Set-ADAccountPassword -Identity $item -NewPassword $newPassword -Reset -credenti ...
      + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          + CategoryInfo          : ObjectNotFound: (C:\resetpasswordbulk.csv:ADAccount) [Set-ADAccountPassword], ADIdentityNotFo 
         undException
          + FullyQualifiedErrorId : Cannot find an object with identity: 'C:\resetpasswordbulk.csv' under: 'DC=***,DC=***'., 
         Microsoft.ActiveDirectory.Management.Commands.SetADAccountPassword
       
       AD Password has been reset for:  C:\resetpasswordbulk.csv 
  • #71894

    Don Jones
    Keymaster

    Ah.

    $filename = Get-Filename -initialDirectory "C:"
    $content = Import-CSV $filename
    
    • #71897

      Stephen Valdinger
      Participant

      Derp. Yeah. That does it. That dialog just gives you a path. You still would need to pass that to Import-CSV. Good catch Don, thanks!

      Your code would look like this with those changes:

      #Create a variable to store the data from your CSV file into.
      $csvItems = Get-Filename -initialDirectory "C:"
      $content = Import-CSV $csvItems
      #You'd pass your variables here, inside a ForEach Loop. Since this is a script that's doing a couple of things,
      #it would be much easier to loop this way rather than piping to ForEach-Object.
      
      Foreach($item in $content){
      
    • #71903

      Chris
      Participant

      Appreciate that Don & Stephen.

      I changed write-host (save the children) I don't think it goes through the loop however, as i am no longer getting a success message and the password of the chosen account isn't changing either, but i get 0 errors. This is becoming slightly annoying, but i'm determined to get it working!

  • #71905

    Don Jones
    Keymaster

    Well, a lot of the problem is that your code is a mess, honestly. I know you're just throwing it together as you go, but that's what's making it harder for you to follow the logic and spot the problems. "Throwing it together" is called "incurring technology debt," and the frustration you're feeling is the interest on that debt. At some point you're going to have to stop, pay off the principal, and do it the right way – at which point you'll stop paying all that annoying interest.

    If you want to post your code as it is right now, I'd be happy to try and help. Longer-term, it'd be worth grabbing something like "The PowerShell Scripting & Toolmaking Book," and really focusing on The One True Way of writing scripts like this. It's a lot faster and less frustrating, I promise, and worth the investment.

    Also, it's puppies, not children.

    • #71909

      Chris
      Participant

      Hi Don, i completely agree, code neatness and good practice is a must and i will follow those principles, I've ordered power shell in a month of lunches and will look into the one you have suggested.

      I've neatened it up, but i can't think of what else to do with it, if you can help, great, if not i will try and think of an alternative, or just stick with importing a static csv!

      Appreciate all your help.

      Import-Module ActiveDirectory
      
      $credpath = 'C:\safe\secretfile.txt'
      $c = Import-Clixml -Path $credpath
      
      Function Get-Filename($initialDirectory)
      {
          [System.Reflection.Assembly]::LoadWithPartialName("System.windows.foms") | Out-Null
      
          $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
          $OpenFileDialog.InitialDirectory = $initialDirectory
          $OpenFileDialog.filter = "CSV (*.csv)| *.csv"
          $OpenFileDialog.ShowDialog() | Out-Null
          $OpenFileDialog.FileName
      }
      
      $csvItems = Get-Filename -initialDirectory "C:"
      $content = Import-CSV $csvItems 
      $newPassword = ConvertTo-SecureString -AsPlainText "password99" -Force
      
       ForEach($item in $content) {
      
      Set-ADAccountPassword -Identity $item -NewPassword $newPassword -credential $c 
       
      Write-Output " AD Password has been reset for: "$item
      }
      
      
  • #71912

    Don Jones
    Keymaster

    $item is an object, as I noted earlier, referring to an entire row of your CSV file. If your CSV has a "samAccountName" column, then you want $item.samAccountName. Regardless of whether that's the only column, Import-CSV always returns objects, not just a collection of simple strings.

    You're probably seeing odd output from Write-Output for this reason, too. I'd also use:

    Write-Output " AD Password has been reset for: $($item.samAccountName)"

    • #71917

      Chris
      Participant

      Thank you very much Don – Adding a column header and incorporating the changes has yielded a positive result. I appreciate the time you spent on this. I hope you have a nice day and i eagerly await the arrival of my new books 😉

You must be logged in to reply to this topic.