Where-Object filtering problem

This topic contains 8 replies, has 2 voices, and was last updated by Profile photo of Don Jones Don Jones 2 years, 1 month ago.

  • Author
    Posts
  • #26210
    Profile photo of Anthony Williams
    Anthony Williams
    Participant

    I need to put an Excel add-in into each users' \AppData\Roaming\Microsoft\Addins folder. This script will run via GP on lab computers, so I'd really like it to only copy the file into a user's folder if it doesn't already exist (so only for new users, in other words).

    This part works:

    # Get all existing users except for Public and only show names
    $Users = Get-ChildItem -Path \Users -Exclude Public -Name 
    
    # For each user, copy the XLAM file into the user's AppData folder
    $users | ForEach-Object -process {COPY -Path "\\hc1\dist$\PackageSources\Applications\Microsoft Office Add-Ins\Excel\TreePlan\186\TreePlan-186-Student-Addin.xlam" -Destination C:\Users\$_\AppData\Roaming\Microsoft\AddIns}
    

    This part doesn't: it returns all users, when I want it to return only users where the xlam file does not exist.

    Get-ChildItem | ?{ $_.PSIsContainer } | Select-Object FullName | Where-Object { -not (Get-ChildItem $_ -File -Recurse -Filter *TreePlan-186*) } | Select-Object Name
    

    Can anyone point me to what I'm missing?

  • #26213
    Profile photo of Don Jones
    Don Jones
    Keymaster

    When you Select-Object FullName, you're creating an object that has a single property, FullName.

    You later use that as an argument to your Get-ChildItem command. I think you're thinking that it'll just be a string, but it isn't – it's an object, that has a property, which contains a string.

    Select-Object -Expand FullName

    Would create a simple string to be passed on to Where-Object.

    Finally, although it might otherwise work, what you're doing with the Boolean logic inside that Where-Object is a little morally wrong. Boolean means "True" or "False;" your Get-ChildItem is returning neither of those, but is instead returning File objects.

    If the goal is to see if the file exists, consider piping Get-ChildItem to Measure-Object, and then to Select -Expand Count. That way, you'll get zero (false) or nonzero (true), which is a lot more Boolean. Of course, by doing so you'd lose your File object, so you couldn't do a Select-Object Name at the end.

  • #26214
    Profile photo of Don Jones
    Don Jones
    Keymaster

    And... sorry, just keep looking at this. If the file you're after is "TreePlan-186," can't you just (assuming $path has your start path):

    Get-ChildItem $path -File -Recurse -Filter "*TreePlan-186*" | Select -First 1 | Measure | Select -Expand Count
    

    ? You'd get either a 0 (file doesn't exist in the path) or 1 (file exists at least once). I'm not sure I get why you're getting all the directories first, and then recursively getting all their files anyway. I added Select -First 1 just so it'd stop running once it finds an instance, saving some time.

  • #26244
    Profile photo of Anthony Williams
    Anthony Williams
    Participant

    Scripting via the PS is new to me, so it's quite likely that I'm doing it badly. But that's why I'm posting, so thank you very much for the help! I know I'm asking elementary questions, but...gotta start somewhere.

    I see the point about the Boolean logic, so thanks.

    I need the script to look through every user's Add-In directory for the Treeplan file and if it exists, do nothing to that directory. But if it doesn't exist, then copy it in.

    I this this is moving towards what I want to do:

    Get-ChildItem -Path C:\Users -Force -File -Recurse -Filter "*TreePlan-186-Student-Addin*" | Measure | Select -Expand Count

    Could I do a ForEach on the GCI and then maybe create an array to act on?

    Also, this GCI has to look through a lot of extraneous folders, many of which it doesn't have permission to. I know where the file will exist if it does: is there a way to tell it look inside that specific folder, and only that folder, inside each user's directory?

  • #26247
    Profile photo of Don Jones
    Don Jones
    Keymaster

    OK. So, you want to copy this on a per-user basis.

    $Users = Get-ChildItem -Path c:\users -Directory
    foreach ($user in $users) {
      $UserPath = $user.FullName
      if (Get-ChildItem -Path $UserPath -Force -File -Recurse -Filter "*TreePlan-186-Student-Addin*" -EA SilentlyContinue | 
        Measure | 
        Select -Expand Count) {
          # copy the file to this user
        }
    }
    

    That'll suppress Access Denied errors. But yes, if you have a pre-set list of subfolders, you could feed those to Get-ChildItem instead of just going down the entire user folder hierarchy. It's a bit more complex. How many "known" locations are we talking about?

  • #26250
    Profile photo of Anthony Williams
    Anthony Williams
    Participant

    Just one: one file has to exist in one folder, for each and every user.

    Let me see if I understand what we're doing: we're finding each user folder and searching recursively in it for the file, measuring it—how are we telling it to act on a 1 or 0?—and then you've left it to me to put the COPY in?

    I can't find -EA in the help file: that must mean continue/ignore errors?

  • #26252
    Profile photo of Don Jones
    Don Jones
    Keymaster

    -EA is an alias for -ErrorAction; look in help_about_common_parameters. All cmdlets have this. And it specifies the error behavior; SilentlyContinue means "shut up and keep going." See, "The Big Book of PowerShell Error Handling."

    And yes, in my example, you need to add the copy command. The logic comes from the If() construct. Because I've set it up to output either 0 (false) or 1 (true)... oh, I've got it backwards.

    $Users = Get-ChildItem -Path c:\users -Directory
    foreach ($user in $users) {
      $UserPath = $user.FullName
      if (-not (Get-ChildItem -Path $UserPath -Force -File -Recurse -Filter "*TreePlan-186-Student-Addin*" -EA SilentlyContinue | 
        Measure | 
        Select -Expand Count)) {
          # copy the file to this user
        }
    }
    

    If it's 1, or True, that'll now be read as False, so no copy. If it's 0, or False, that'll now be read as True, and it'll copy. Once you add the code to copy.

  • #26271
    Profile photo of Anthony Williams
    Anthony Williams
    Participant

    Thanks! This now works exactly like I want it to. You gave me a lot of help, Don, and I really appreciate it.

    This'll work fine, but for my education, how would I tell it to look only in a given folder? I imagine it'd be something like:

    Get-ChildItem -Path C:\Users\SOME VARIABLE\rest of path
  • #26278
    Profile photo of Don Jones
    Don Jones
    Keymaster

    yeah, or you can also pass an array of paths to the -Path variable.

You must be logged in to reply to this topic.