Author Posts

June 9, 2015 at 10:38 am

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?

June 9, 2015 at 11:35 am

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.

June 9, 2015 at 11:39 am

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.

June 10, 2015 at 6:31 am

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?

June 10, 2015 at 6:40 am

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?

June 10, 2015 at 7:32 am

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?

June 10, 2015 at 7:44 am

-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.

June 10, 2015 at 9:22 am

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

June 10, 2015 at 10:19 am

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