Specify NON-hardcoded path and filename in a scriptblock? - powershell

Ok I feel spechul for even asking, but I have tried several iterations of this and nothing has worked except hard coding the scriptname in the Scriptblock statement, which is unacceptable.
Here is the code that works, hard coded, unacceptable....
$Scriptblock = { C:\Scripts\Path1\ScriptName.ps1 -arguement0 $args[0] -arguement1 $args[1] }
Start-Job -ScriptBlock $Scriptblock -ArgumentList $argue0, $argue1 | Out-Null
Ive tried this, and it doesn't work...
$loc = (Get-Location).Path
Set-Location -Path $loc
And this....
$rootpath = $MyInvocation.MyCommand.Path.Substring(0, ($MyInvocation.MyCommand.Path).LastIndexOf("\"))
Set-Location -Path $rootpath
And this....
$rootpath = $MyInvocation.MyCommand.Path.Substring(0, ($MyInvocation.MyCommand.Path).LastIndexOf("\"))
$scriptFilename = $([string]::Format("{0}\ScriptName.ps1", $rootpath))
$sb = $([string]::Format("{0} -arguement0 $args[0] -arguement1 $args[1]", $scriptFilename))
$Scriptblock = { $sb }
Start-Job -ScriptBlock $Scriptblock -ArgumentList $argue0, $argue1 | Out-Null
Nothing else has worked except the first code above with hardcoded path and script name - I know it has to be something stupid I am missing - help me fix stoopid please! ;-)

In your last example, this line:
$ScriptBlock = { $sb }
simply creates a scriptblock with a string inside it. Change it to:
$ScriptBlock = [scriptblock]::Create($sb)

Related

PowerShell Test-Path not working in scriptblock

I have the following PowerShell code that tests if a file exists within a script block:
$scriptblock =
{
Param($filename)
return "Scriptblock filename $filename Exists? -> $(Test-Path $filename)"
}
$myFilename = "MyFile.xml"
Write-Host "Main filename $myFilename Exists? -> $(Test-Path $myFilename)"
$job = Start-Job -Name "myJob" -ScriptBlock $scriptBlock -ArgumentList $myFilename
$result = Receive-Job -Name "myJob"
Write-Host $result
When I run it I get the following output indicating the file exists in the main execution but not in the script block.
Main filename MyFile.xml Exists? -> True
Scriptblock filename MyFile.xml Exists? -> False
Can someone please indicate what is needed to test for file existence in a script block?
Thanks!
As a best practice, you should probably be including the full path to the file you want tested, rather than relying on the current directory (which can vary if you run the script under a different user context).
$scriptblock = {
param($filename)
"Scriptblock filename $filename Exists? -> $(Test-Path $filename)"
}
$myFilename = "C:\Temp\MyFile.xml"
"Main filename $myFilename Exists? -> $(Test-Path $myFilename)"
Start-Job -Name "myJob" -ScriptBlock $scriptBlock -ArgumentList $myFilename
Receive-Job -Name "myJob"

Background-Job run Scripts without hardcoding them

I am running a PowerShell GUI that calls different scripts. I'm currently using hardcoded paths such as:
function start-jobhere([scriptblock]$block) {
Start-Job -ArgumentList (Get-Location),$block {
Set-Location $args[0];
Invoke-Expression $args[1]
}
}
$handler_button1_Click = {
$job1 = start-jobhere {& K:\Uploader\Import\DataUploader.ps1} -Name "Uploader"
}
I tried to avoid it using:
$LocalPath = ($MyInvocation.MyCommand.Path).ToLower().Replace("datauploader.ps1", "")
$handler_button1_Click = {
$job1 = start-jobhere {& $LocalPath\DataUploader.ps1} -Name "Uploader"
}
But it doesn't seem to work. I have some scripts in the same folder and some are on a different harddrive. Is there a way to avoid the hardcoded paths?
You're passing a scriptblock to the function. The code inside the scriptblock doesn't know anything about variables in the rest of the script unless you make them known via the using scope modifier:
$job1 = start-jobhere {& "$using:LocalPath\DataUploader.ps1"} -Name "Uploader"
With that said, if you want to run scripts anyway, why aren't you passing their path as a string to your function?
function start-jobhere([string]$Name, [string]$Script) {
Start-Job -Name $Name -ScriptBlock {
Set-Location $args[0]
& $args[1]
} -ArgumentList (Get-Location), $Script
}
$handler_button1_Click = {
$job1 = start-jobhere "$LocalPath\DataUploader.ps1" -Name "Uploader"
}

Waiting for copy process to finish

Is there any way to wait for the copy process to finish before running another command?
I tried Start-job and Wait-Job, but it doesn't work.
$func = {
function move-tozip
{
param([string]$filedest)
$Shell = New-Object -com Shell.Application
$b = $shell.namespace($zippath.ToString())
$b.CopyHere($filedest.tostring())
#Remove-Item -Path $filedest
}
}
start-job -InitializationScript $func -ScriptBlock {move-tozip $args[0]} -ArgumentList $file
The easiest way to wait for a job to complete is to give it a name and tell Wait-Job to wait on the task with that name, your script will wait for the job with the name WaitForMe to complete and then run the rest of your code once it has.
Using the -Name paramter with your code below:
$func =
{
function Move-ToZip
{
Param([string[]]$path, [string]$zipfile)
if (-not $zipfile.EndsWith('.zip')) {$zipfile += '.zip'}
if (-not (test-path $zipfile))
{
set-content $zipfile ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
}
$shell = (new-object -com shell.application).NameSpace($zipfile)
foreach($file in $path)
{
$shell.CopyHere($file)
start-sleep -milliseconds 100
}
}
}
Start-Job -Name "WaitForMe" -InitializationScript $func -ScriptBlock {Move-ToZip -path $args[0] -zipfile $args[1]} -ArgumentList "D:\data.log", "D:\datazip.zip"
Write-Host "Waiting for job to complete"
Wait-Job -Name "WaitForMe"
Write-Host "Job has completed :D"
To zip one file or folder
-ArgumentList "D:\testfile.log", "D:\datazip.zip"
To zip multiple files or folders
-ArgumentList #("D:\testfile.log","D:\testFolder1"), "D:\testzip.zip"
EDIT 17/12/2015
I've adapted code from This MSDN blog to the Move-ToZip function as the previous code didnt work for me at all, i've tested the above code successfully on files and folders. I have not tested the performance of this method, if you wish to compress/zip multiple large files/folders i would highly suggest looking into using a known working library or third party utility like 7zip.

PowerShell Splatting the Argumentlist on Invoke-Command

How is it possible to use the parameters collected in a hash table for use with ArgumentList on Invoke-Command?
$CopyParams = #{
Source = 'E:\DEPARTMENTS\CBR\SHARE\Target'
Destination = 'E:\DEPARTMENTS\CBR\SHARE\Target 2'
Structure = 'yyyy-MM-dd'
}
Invoke-Command -Credential $Cred -ComputerName 'SERVER' -ScriptBlock ${Function:Copy-FilesHC} -ArgumentList #CopyParams
Whatever I try, it's always complaining about the 'Source':
Cannot validate argument on parameter 'Source'. The "Test-Path $_" validation script for the argument with
value "System.Collections.Hashtable" did not return true. Determine why the validation script failed
This blog talks about a similar problem, but I can't get it to work.
The same is true for a simple Copy-Item within Invoke-Command, example:
Invoke-Command -Credential $Cred -ComputerName 'SERVER' -ScriptBlock {Copy-Item} -ArgumentList #CopyParams
Invoke-Command : Missing an argument for parameter 'ArgumentList'. Specify a parameter of type 'System.Obj
ect[]' and try again.
At line:11 char:89
+ ... ck {Copy-Item} -ArgumentList #CopyParams
Thank you for your help.
One-liner, to convert a remote script to accept named parameters from a hash.
Given a scriptblock which you wish to call like this:
$Options = #{
Parameter1 = "foo"
Parameter2 = "bar"
}
Invoke-Command -ComputerName REMOTESERVER -ArgumentList $Options -ScriptBlock {
param(
$Parameter1,
$Parameter2
)
#Script goes here, this is just a sample
"ComputerName: $ENV:COMPUTERNAME"
"Parameter1: $Parameter1"
"Parameter2: $Parameter2"
}
You can convert it like so
Invoke-Command -Computername REMOTESERVER -ArgumentList $Options -ScriptBlock {param($Options)&{
param(
$Parameter1,
$Parameter2
)
#Script goes here, this is just a sample
"ComputerName: $ENV:COMPUTERNAME"
"Parameter1: $Parameter1"
"Parameter2: $Parameter2"
} #Options}
What's going on? Essentially we've wrapped the original script block like so:
{param($Options)& <# Original script block (including {} braces)#> #options }
This makes the original script block an anonymous function, and creates the outer script block which has a parameter $Options, which does nothing but call the inner script block, passing #options to splat the hash.
Here's one way to approach passing named parameters:
function Copy-FilesHC
{
param ($Source,$Destination,$Structure)
"Source is $Source"
"Desintation is $Destination"
"Structure is $Structure"
}
$CopyParams = #{
Source = 'E:\DEPARTMENTS\CBR\SHARE\Target'
Destination = "'E:\DEPARTMENTS\CBR\SHARE\Target 2'" #Nested quotes required due to embedded space in value.
Structure = 'yyyy-MM-dd'
}
$SB = [scriptblock]::Create(".{${Function:Copy-FilesHC}} $(&{$args}#CopyParams)")
Invoke-Command -Credential $Cred -ComputerName 'SERVER' -ScriptBlock $SB
Basically, you create a new script block from your invoked script, with the parameters splatted to that from the hash table. Everything is already in the script block with the values expanded, so there's no argument list to pass.
I found a workaround, but you have to make sure that your Advanced function which is located in your module file is loaded up front in the local session. So it can be used in the remote session. I wrote a small helper function for this.
Function Add-FunctionHC {
[CmdletBinding(SupportsShouldProcess=$True)]
Param(
[String]$Name
)
Process {
Try {
$Module = (Get-Command $Name -EA Stop).ModuleName
}
Catch {
Write-Error "Add-FunctionHC: Function '$Name' doesn't exist in any module"
$Global:Error.RemoveAt('1')
Break
}
if (-not (Get-Module -Name $Module)) {
Import-Module -Name $Module
}
}
}
# Load funtion for remoting
Add-FunctionHC -Name 'Copy-FilesHC'
$CopyParams = #{
Source = 'E:\DEPARTMENTS\CBR\SHARE\Target\De file.txt'
Destination = 'E:\DEPARTMENTS\CBR\SHARE\Target 2'
}
$RemoteFunctions = "function Copy-FilesHC {${function:Copy-FilesHC}}" #';' seperated to add more
Invoke-Command -ArgumentList $RemoteFunctions -ComputerName 'SERVER' -Credential $Cred -ScriptBlock {
Param (
$RemoteFunctions
)
. ([ScriptBlock]::Create($RemoteFunctions))
$CopyParams = $using:CopyParams
Copy-FilesHC #CopyParams
}
The big advantage is that you don't need to copy your complete function in the script and it can stay in the module. So when you change something in the module to the function it will also be available in the remote session, without the need to update your script.
I recently experienced a similar problem and solved it by building the hash (or rebuilding the hash) inside the invoke by leveraging the $using variable scope (more on that here)
it looks something like this:
$Source = 'E:\DEPARTMENTS\CBR\SHARE\Target'
$Destination = 'E:\DEPARTMENTS\CBR\SHARE\Target 2'
$Structure = 'yyyy-MM-dd'
Invoke-Command -Credential $Cred -ComputerName 'SERVER' -ScriptBlock {
$CopyParms= #{
'Source'=$Using:Source
'Destination'=$Using:Destination
'Structure'=$Using:Structure
}
Function:Copy-FilesHC #CopyParms
}
This is what works for me:
$hash = #{
PARAM1="meaning of life"
PARAM2=42
PARAM3=$true
}
$params = foreach($x in $hash.GetEnumerator()) {"$($x.Name)=""$($x.Value)"""}
I know this is late, but I ran into the same problem and found a solution that worked for me. Assigning it to a variable within the scriptblock and then using that variable to splat didn't show any problems.
Here's an example:
$param=#{"parameter","value"}
invoke-command -asjob -session $session -ScriptBlock {$a=$args[0];cmdlet #a } -ArgumentList $param

Invoke-Command with dynamic function name

I found this awesome post: Using Invoke-Command -ScriptBlock on a function with arguments
I'm trying to make the function call (${function:Foo}) dynamic, as in I want to pass the function name.
I tried this:
$name = "Foo"
Invoke-Command -ScriptBlock ${function:$name}
but that fails. I also tried various escape sequences, but just can't get the function name to be dynamic.
EDIT: For clarity I am adding a small test script. Of course the desired result is to call the ExternalFunction.
Function ExternalFunction()
{
write-host "I was called externally"
}
Function InternalFunction()
{
Param ([parameter(Mandatory=$true)][string]$FunctionName)
#working: Invoke-Command -ScriptBlock ${function:ExternalFunction}
#not working: Invoke-Command -ScriptBlock ${invoke-expression $FunctionName}
if (Test-Path Function:\$FunctionName) {
#working,but how to use it in ScriptBlock?
}
}
InternalFunction -FunctionName "ExternalFunction"
Alternate solution:
function foo {'I am foo!'}
$name = 'foo'
$sb = (get-command $name -CommandType Function).ScriptBlock
invoke-command -scriptblock $sb
I am foo!
as simple as :
invoke-expression $name
or if you want to keep invoke-commande for remoting for example
Invoke-Command -ScriptBlock { invoke-expression $name}
You could try the following. It tests if the name specified is a valid function before it attempts to run it:
$myfuncnamevar = "Foo"
Invoke-Command -ScriptBlock {
param($name)
if (Test-Path Function:\$name) {
#Function exists = run it
& $name
}
} -ArgumentList $myfuncnamevar