I have a script based powershell module (.psm1) and I have it imported into my main script. This module however needs to call a batch file that is located inside its same directory but apparently it is unable to see it. Currently the function in question looks like this:
function MyFunction
{
& .\myBatch.bat $param1 $param2
}
How can I make the function see the batch file?
. is the current working directory, not the directory in which the module resides. The latter can be determined via the MyInvocation variable. Change your function to this:
function MyFunction {
$Invocation = (Get-Variable MyInvocation -Scope 1).Value
$dir = Split-Path $Invocation.MyCommand.Path
$cmd = Join-Path $dir "myBatch.bat"
& $cmd $param1 $param2
}
Try this:
function MyFunction {
& (Join-Path (Split-Path $MyInvocation.MyCommand.Path) 'myBatch.bat') $param1 $param2
}
Related
I know this is probably a very basic question and should exist in the PowerShell documentation, but the point is that I don't exactly know how to look up the answer to this.
The question is: how to prevent a variable from being applied before being requested in code?
Example:
#Variable
$MoveToTest = Move-Item -Path "C:\Test.jpg" -Destination "C:\Test Folder" -force -ea 0
#Code
If (Test-Path C:\*.mp4) {
$MoveToTest
}
The If condition asks for the .jpg file to be moved if there is any .mp4 file in the -Path, however the way the code is written the variable is applied before availing the If condition.
Sorry, I know this is probably very basic, but as I'm learning by myself and according to everyday needs, I ended up missing some basic principles.
You can defer execution of a piece of code by wrapping it in a ScriptBlock {...}:
# Variable now contains a scriptblock with code that we can invoke later!
$MoveToTest = { Move-Item -Path "C:\Test.jpg" -Destination "C:\Test Folder" -force -ea 0 }
# Code
If (Test-Path C:\*.mp4) {
# Use the `&` invocation operator to execute the code now!
&$MoveToTest
}
This is similar to how the function keyword works - it simply associates the scriptblock with a function name instead of a variable - and of course you could define a function as well:
function Move-TestJpgToTestFolder {
Move-Item -Path "C:\Test.jpg" -Destination "C:\Test Folder" -force -ea 0
}
# Code
If (Test-Path C:\*.mp4) {
# We no longer need the `&` invocation operator to execute the code, PowerShell will look up the function name automatically!
Move-TestJpgToTestFolder
}
I'm learning the powershell. Currently I have a tough requirement. I need to call an powershell script(ps1) in parallel from an powershell module(psm1). The ps1 task is like following
param(
[Parameter(Mandatory=$true)]
[String] $LogMsg,
[Parameter(Mandatory=$true)]
[String] $FilePath
)
Write-Output $LogMsg
$LogMsg | Out-File -FilePath $FilePath -Append
The FilePath is like "C:\Users\user\Documents\log\log1.log"
And in the psm1 file, I use the runspacepool to do async task. Like the following demo
$MaxRunspaces = 5
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxRunspaces)
$RunspacePool.Open()
$Jobs = New-Object System.Collections.ArrayList
Write-Host $currentPath
Write-Host $lcmCommonPath
$Filenames = #("log1.log", "log2.log", "log3.log")
foreach ($File in $Filenames) {
Write-Host "Creating runspace for $File"
$PowerShell = [powershell]::Create()
$PowerShell.RunspacePool = $RunspacePool
$FilePath = -Join("C:\Users\user\Documents\log\",$File)
$PowerShell.AddScript("C:\Users\user\Documents\foo.ps1").AddArgument($FilePath) | Out-Null
$JobObj = New-Object -TypeName PSObject -Property #{
Runspace = $PowerShell.BeginInvoke()
PowerShell = $PowerShell
}
$Jobs.Add($JobObj) | Out-Null
}
But there are two serious problem.
Can't pass the parameters to ps1 file.
I just try to create the file path in the ps1 file side, it works and file created. But when I try to pass the argument from psm1 file. The files are not created. I also try to use script block and it can pass the parameters. But since my ps1 code is too large(The above is just part of it), using script block is unreal. I need a method to pass parameter to ps1 file.
Can't get write-host information in ps1 file while psm1 is still running
If the runspacepool has limitation for passing the parameters to ps1 file, is there any other solution to deal with the async task for powershell script? Thanks.
Can't pass the parameters to ps1 file.
Use AddParameter() instead of AddArgument() - this will allow you to bind the argument to a specific parameter by name:
$PowerShell.AddScript("C:\Users\user\Documents\foo.ps1").
AddParameter('FilePath', $FilePath).
AddParameter('LogMsg', 'Log Message goes here') | Out-Null
Can't get write-host information in ps1 file while psm1 is still running
Correct - you cannot get host output from a script not attached to the host application's default runspace - but if you're using PowerShell 5 or newer you can collect the resulting information from the $PowerShell instance and relay that if you want to:
# Register this event handler after creating `$PowerShell` but _before_ calling BeginInvoke()
Register-ObjectEvent -InputObject $PowerShell.Streams.Information -EventName DataAdded -SourceIdentifier 'WriteHostRecorded' -Action {
$recordIndex = $EventArgs.Index
$data = $PowerShell.Streams.Information[$recordIndex]
Write-Host "async task wrote '$data'"
}
The folder structure is:
--root
--root\source-code\
--root\powershell-scripts\
I need the method below that is inside the \powershell-scripts folder to target files inside \source-code:
function Test($param)
{
dir -Include ASourceCodeFile.txt -Recurse |
% { SomeMethod $_ $param }
}
What am I missing?
The $PSScriptRoot automatic variable contains the path of the directory in which the current script is located. Use Split-Path to find its parent (your --root) and Join-Path to get the path to the source-code folder:
Join-Path -Path (Split-Path $PSScriptRoot -Parent) -ChildPath 'source-code'
$PSScriptRoot was introduced in PowerShell 3.0
A bit late, but maybe still helpful for someone:
Directory structure :
MyRoot\script\scriptrunning.ps1
config:
MyRoot\config.xml
to read the xml file from scriptrunning.ps1:
[xml]$Config = Get-Content -path "${PSScriptRoot}\..\config\config.xml"
if you have a script in --root\powershell-scripts\ and you want to reference something in --root\source-code\ or say get-content you can do this:
cd --root\powershell-scripts\
get-content '..\source-code\someFile.txt'
The ..\ references the parent directory which contains \source-code\ and then you reference or pull in file or scripts from that directory.
this was a trick that I used in vbs that I converted to PS...
$scriptPath = Split-Path $MyInvocation.MyCommand.Path -Parent
$a = $scriptPath.split("``\``") for ($i = 0 ; $i -lt $a.count-1 ; $i++){
$parentDir = $parentDir + $a[$i] <br>
if($i -lt $a.count-2){$parentDir = $parentDir + "``\``"}
}
Write-Output $parentDir
My C#-program should be by opening perform a powershellfunction, looks like this:
function RunIE
{
$a = Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion -name ProgramFilesDir
$path = $a.ProgramFilesDir + "\Internet Explorer\iexplore.exe"
& $path "www.heise.de" -extoff
Start-Sleep 6
$shell = (New-Object -COM Shell.Application)
$ie = #($shell.Application.Windows()) | Where-Object { $_.LocationUrl -like "*heise*" }
Write-Output $ie
}
$ScriptLog.Invoke([log4net.Core.Level]::Debug, "Funktionsdurchlauf durchgeführt")
The log at the end will written in my logfile. (So I think this works.)
During the C#-program runs an other script should be conducted, like this:
...
$ie = RunIE
#here I get a mistake: "The term 'RunIE' is not recognized as the name of a
#cmdlet, function, script file, or operable program. Check the spelling of the
#name, or if a path was included, verify that the path is correct and try again."
$ie.Navigate2($path)
Start-Sleep 5
...
How can I use my function in other scripts, without new/second invocation? (I think the solution must create in powershell.)
thanks.
You need to dot source the script that contain the functions and script-scope variables you want to use in your script. For instance, if IEUtils.ps1 contained your RunIE function then you would dot source it into primary script like so:
. C:\Temp\IEUtils.ps1
$ie = RunIE
This assumes that IEUtils.ps1 in C:\Temp. Subtitute the appropriate dir as required. If it is in the same dir as your primary script then use:
$ScriptPath = $MyInvocation.MyCommand.Path
$ScriptDir = Split-Path -parent $ScriptPath
. $ScriptDir\IEUtils.ps1
I have a function that lets me write the file-path of files to a text file, depending on your input. That sounds confusing, but I don't know of a better way to put it, so here's the function:
Function writeAllPaths([string]$fromFolder,[string]$filter,[string]$printfile) {
Get-ChildItem -Path $fromFolder -Recurse $filter | Select-Object -Property FullName > $printfile
}
First argument being the folder you start your search from.
Second argument, the filter. *.zip for instance, will list all zip files.
Third argument, you have to provide where the text file will end up.
Sample usage: writeAllPaths c:\ *.zip c:\allZips.txt
The thing is, when I do this, Powershell won't accept commands until it's done. Which isn't very productive. Is there a way to have this run in the background when started. And preferably, give a little message when it's done. I could be opening any file that's being created in the middle of the process...
Also, I'm on Windows 7, so I'm guessing that I have Powershell 2.0
Yeah, I'm not sure about it :p
EDIT:
I used Start-Job, as suggested, as follows:
Function writeAllPaths([string]$fromFolder,[string]$filter,[string]$printfile) {
Start-Job -ScriptBlock {Get-ChildItem -Path $fromFolder -Recurse $filter | Select-Object -Property FullName > $printfile}
}
However, the file isn't created. The old function does create a file.
EDIT2: it would be preferable to have this function in my Powershell profile. This way, I can execute it whenever I want, instead of having to load in the specific ps1 file every time I boot Powershell.
More info on the Powershell profile can be found here
You can summon your own profile by typing: notepad $profile
In the new scope you create for the background job, your parameters defined for you WriteAllPaths function are not defined. Try this and you'll see they aren't:
Function writeAllPaths([string]$fromFolder,[string]$filter,[string]$printfile)
{
Start-Job { "$fromFolder, $filter, $printFile" }
}
$job = WriteAllPaths .\Downloads *.zip zips.txt
Wait-Job $job
Receive-Job $job
, ,
Try this instead:
Function writeAllPaths([string]$fromFolder, [string]$filter, [string]$printfile)
{
Start-Job {param($fromFolder,$filter,$printfile)
"$fromFolder, $filter, $printfile" } `
-ArgumentList $fromFolder,$filter,$printfile
}
$job = WriteAllPaths .\Downloads *.zip z.txt
Wait-Job $job
Receive-Job $job
.\Downloads, *.zip, z.txt
Now you see your output because we passed the parameters through to the scriptblock via -ArgumentList. What I would recommend is a function that can optionally use a background job. Just stick this function definition in your profile and you're set:
function WriteAllPaths([string]$FromFolder, [string]$Filter,
[string]$Printfile, [switch]$AsJob)
{
if (![IO.Path]::IsPathRooted($FromFolder)) {
$FromFolder = Join-Path $Pwd $FromFolder
}
if (![IO.Path]::IsPathRooted($PrintFile)) {
$PrintFile = Join-Path $Pwd $PrintFile
}
$sb = {
param($FromFolder, $Filter, $Printfile)
Get-ChildItem $FromFolder -r $filter | Select FullName > $PrintFile
}
if ($AsJob) {
Start-Job $sb -ArgumentList $FromFolder,$Filter,$PrintFile
}
else {
& $sb $FromFolder $Filter $PrintFile
}
}
Test the function (synchronously) like so:
$job = WriteAllPaths Downloads *.zip z.txt -AsJob
Wait-Job $job
Receive-Job $job
Note that I'm testing if the path is root and if not I'm prepending the current dir. I do this because the background job's starting dir is not always the same as where you executed Start-Job from.
Which version of powershell? In Powershell 2.0 I think you can use background jobs for this Start-Job.
Starts a Windows PowerShell background job.