Confusion on function to create directory on remote computer

I am struggling to create a function to do this using New-PSession and importing list of computers from a text file. I think my confusion is in two areas :-

  1. Do I still need to use the Foreach construct, or will the PSession connection to eachj computer handle this part ?
  2. Parameters - some do not work (like $path) if declared outside of the PSSession - get unable to determine $path ?

Have reproduced what I have so far below. The first is running this as a script which works fine. The second is after trying to convert to a function - I cannot get how to do this…

  1. Non-function (works)

#######################################
##Check existence of WSP Share ##
######################################

Set-Location -Path D:\Updates
$Computers = get-content ".\computers.txt"
$s = New-PSSession -Computername $Computers
$command = {
## Variables
$path = "d:\WSP"
$share = "WSP"
## Create Folder
IF (!(test-path $path)){
write-host "Creating folder: " $path -ForegroundColor green
New-Item -Path $path -ItemType directory
} else {
write-host "The folder already exists: "$path -ForegroundColor Yellow
}
 
## Create Share
IF (!(Get-SmbShare -Name $share -ErrorAction SilentlyContinue)) {
write-host "Creating share: " $share -ForegroundColor green
New-SmbShare –Name $share –Path $path –Description 'Test Shared Folder' –FullAccess Administrator -ChangeAccess Everyone
} else {
write-host "The share already exists: " $share -ForegroundColor Yellow
}
}

Invoke-Command -Session $s -scriptblock $command
  1. Function (not working)
Function Check-Share {
param($username = 'testdomain\spdevadmin',
$password = 'DevSP13',
$cred = (New-Object System.Management.Automation.PSCredential -ArgumentList @($username,(ConvertTo-SecureString -String $password -AsPlainText -Force))),
$Computers = (get-content ".\computers.txt")
            )
$s = New-PSSession -Computername $Computers -Credential $cred
$command = {param(
            $path = 'd:\wsp',
            $share = 'WSP'
                  )
        Foreach ($computer in $computers) {
            if (!(test-path $path)){
                write-host "Creating folder: $path on computer : $computer"  -ForegroundColor green
                New-Item -Path $path -ItemType directory
                                   }
            else {
                write-host "The folder already exists: "$path -ForegroundColor Yellow
                 }
               }
            }
Invoke-Command -Session $s -scriptblock $command
        }
 
## Create Share
IF (!(Get-SmbShare -Name $share -ErrorAction SilentlyContinue)) {
write-host "Creating share: " $share -ForegroundColor green
New-SmbShare –Name $share –Path $path –Description 'Test Shared Folder' –FullAccess Administrator -ChangeAccess Everyone
} else {
write-host "The share already exists: " $share -ForegroundColor Yellow
}
}

Invoke-Command -Session $s -scriptblock $command

That’s a lot of code. It’d help if you could maybe narrow things down to just one question at a time, and preferably use code formatting as indicated above the post text box. But I’ll try to answer your questions generally, to se if that helps.

  1. Do I still need to use the Foreach construct, or will the PSession connection to eachj computer handle this part ?

New-PSSession can accept multiple computer names, and it will create one session object for each. This doesn’t mean you do or do not need a ForEach loop; it depends on whether you need to manage each session individually or if you can just run them as a group.

  1. Parameters – some do not work (like $path) if declared outside of the PSSession – get unable to determine $path ?

When you run commands on a remote computer, they only have access to things ON that remote computer. They can’t go back and access variables that were declared on your LOCAL computer. However, when using Invoke-Command, there are technique you can use to pass local variables TO the remote computers. The -ArgumentList parameter, for example, or in v3 and later the $using: modifier.

Thanks for insanely quick reply !!

I guess if we took just the first bit of code which works, what I am trying to understand is the correct way to turn this into a function. Ultimately I need to be able to feed the parameter values from xml input files, but this is stage 2 - I think best to convert to a function successfully first ?
So really, I am looking for HOW you would convert my code to a function (you don’t need to write it all unless you feel dreadfully sorry for me :slight_smile:

Thanks again

There are multiple problems.

This:

Invoke-Command -Session $s -scriptblock $command

Uses $path and $share internally, but those are not being passed in.

Invoke-Command -Session $s -scriptblock $command -ArgumentList $z,$x

Would pass local variable $z to the remote $path, and local variable $x to the remote $share. These would be the same values for all computers, since you’re targeting all sessions in $s (which is every session you’ve created).

Inside the remote command, you’re doing this:

Foreach ($computer in $computers) {

But the remote command doesn’t define $computers, and in any event, the command is being run INDIVIDUALLY on each computer. In other words, what you send with Invoke-Command is what ONE computer should do TO ITSELF only.

You’ve also got an Invoke-Command inside the command that each computer is running.

The thing is, you’ve kind of walked yourself down a bad path. I think you should just scrap what you’ve done and start over. You can’t take a script that’s designed to work one way and “convert” it to a function that uses Remoting - it usually isn’t that straightforward.

  1. What does EACH computer need to do to itself? Keep in mind that, by default, each computer will only have access to LOCAL resources - they can’t go grabbing stuff from UNCs or other remote locations, unless you do additional configuration.

  2. What information does EACH computer need to do its local work?

The answer to #1 is what goes in your remote command’s script block. The answer to #2 is what gets parameterized in that script block.

$command = {
  param($source,$destination)
  Copy-Item $source $destination
}

$sessions = New-PSSession -ComputerName (Get-Content machines.txt)
Invoke-Command -Session $sessions -Script $command -ArgumentList "c:\start.txt","c:\end.txt"
$sessions | Remove-PSSession

That will copy c:\start.txt to c:\end.txt on each computer listed in machines.txt. There’s no way, using this exact technique, to provide a different source/destination to each computer. If that was the goal, you’d need to re-structure this.

Not knowing what your goals are, I’m not sure I can be more specific. But the thing here is to stop and think about what you’re asking PowerShell to do, line by line, and to keep track of which code is being run locally and which code is being run remotely on each computer.

So is it OK to declare some parameters outside of the remote session, or should you declare ALL parameters inside the remote session.

What the script should do is this.

Take a list of computers from computers.txt (hosted on SOURCE computer) and store them in an array variable. Then open a remote session to each computer and do the following :-

  1. Check if the directory already exists - if yes output message and continue with next part of script (NOT included).
  2. If NOT exist, create directory.
    3.Confirm directory created successfully.

I wanted to create a function so that this can be easily re-used and/or included in other scripts. I need to parameterize so that most (if not all) can be read in from xml files. The remote path will be the same in all cases (D:\WSP in this case).

Really appreciate your help - working through your first book currently :slight_smile:

Well… “is it OK” is a moral question ;). All that matters is that the data be in the right place.

$computers = Get-Content machines.txt
$source = "c:\test.txt"
$dest = "d:\test.txt"
foreach ($computer in $computers) {
  Invoke-Command -computername $computer -script { copy-item $using:source $using:dest }
}

That has values declared locally, two of which are used by the remotely-run script block.That isn’t the only syntax you could use (you’ll notice I used $using: and not -ArgumentList and a param() block), but it’s legal.

But I’m not sure which part of this you’re wanting to make into a function. This “check a bunch of computers to see if a directory exists, and make it if it doesn’t” doesn’t sound like a highly re-usable task. But, if it is:

function Set-DirectoryExistence {
  param( [string[]]$computername, [string]$folder)
  foreach ($computer in $computername) {
   Invoke-Command -computer $computer -script { 
      # the below line runs remotely
      if (-not (test-path $using:folder)) { mkdir $using:folder } 
   }
 }
}

Set-DirectoryExistence -computer (Get-content machines.txt) -folder c:\whatever

Something like that. I just want to make sure you understand WHY that works, where the data is being used, etc.

Thanks Don

Guess I still have a lot to learn :slight_smile:

I will look up the use of $using as not encountered that until now.