Dot sourcing a PowerShell script not ending with .ps1 - powershell

From a PowerShell program, I can "dot source" another PowerShell program. i.e I can execute it as if it were written inside the first one.
Example:
Write-Host 'before'
. MyOtherProgram.ps1
Write-Host 'after'
MyOtherProgram in 'included' inside the main program, exactly as if its content had been copy/pasted.
The problem is: I can only dot source a filename finishing with .ps1
I can't with MyOtherProgram.lib or MyOtherProgram.whatever
Anyone have a method to dot source a PowerShell script not ending with .ps1 ?

Another way would be using Invoke-Expression:
$code = Get-Content ./MyOtherProgram.lib | Out-String
Invoke-Expression $code

I'm not aware if this is compiled into PowerShell or if it's configurable but one way to do it is just have your script temporarily rename it, import it and then rename it back.
Rename-Item C:\Path\MyScript.whatever C:\Path\MyScript.ps1
. C:\Path\MyScript.ps1
Rename-Item C:\Path\MyScript.ps1 C:\Path\MyScript.whatever

Related

How to save function in powershell?

Hi so i've tried to make my first function. A simple one that will restart powershell in windows terminal. And it's working.
function Restart-PowerShell{
Get-Process -Id $PID | Select-Object -ExpandProperty Path | ForEach-Object { Invoke-Command { & "$_" } -NoNewScope }
But when i restart powershell the function disappear, it doesn't save it. How can i save it? I've tried to look around but couldn't find any way to save
You save PowerShell functions by simply putting their definition in a file on disk:
"function Restart-PowerShell {${function:Restart-PowerShell}}" |Set-Content Restart-PowerShell.ps1
This will write it to a file Restart-PowerShell.ps1 in the current directory.
Next time you need to re-use your function, it's as simple as dot-sourcing the file:
PS ~> . .\path\to\Restart-PowerShell.ps1
PS ~> Restart-PowerShell # now the function is available in the current session
Mathias R. Jessen's helpful answer shows how to save your function's definition to a custom file that you can dot-source on demand in future sessions.
However, there is a file that is dot-sourced automatically when a PowerShell session starts (unless explicitly suppressed with -NoProfile via the PowerShell CLI): your $PROFILE file, and that's where customizations of your sessions - such as custom functions and aliases - are typically stored.
Therefore, if you add your function to your $PROFILE file, it automatically becomes available in future sessions too.
You can open $PROFILE in your text editor or, building on Mathias' technique, add the function programmatically, as follows, which ensures on-demand creation of the file and its parent directory (on a pristine machine, neither exists):
# Make sure the $PROFILE file exists.
If (-not (Test-Path $PROFILE)) { $null = New-Item -Force $PROFILE }
# Append the function definition to it.
#"
function Restart-PowerShell {
${function:Restart-PowerShell}
}
"# | Add-Content $PROFILE
Note: To reload your profile mid-session after having modified $PROFILE, use . $PROFILE.

How to execute a string in a variable in powershell

I have the following string
"C:\ProgramData\Package
Cache{6b95042e-f763-4850-9136-d004dd0d0a9b}\AzInfoProtection.exe"
/uninstall
I need to execute the above string as below
First-line
cd C:\ProgramData\Package Cache\{6b95042e-f763-4850-9136-d004dd0d0a9b}
The second line (note there is no exe)
AzInfoProtection /uninstall
Variables are generally executed like below in PowerShell
Invoke-Expression $cmd
But how to split the above string into multiple lines for execution. Then I need to remove the quote and then exe.
It's a bit hard to understand the ask here but I think I follow. Let me know if I'm off base or misunderstanding what you're trying to do.
$commandString = '"C:\ProgramData\Package Cache{6b95042e-f763-4850-9136-d004dd0d0a9b}\AzInfoProtection.exe" /uninstall'
# Get command parent directory
if( $commandString -match '^".*?"' ) {
$runInDir = Split-Path -Parent $Matches[0]
}
# Change directories (use the location stack for easy traversal)
Push-Location $runInDir
# Run program
Invoke-Expression $commandString
# Change back to previous directory
Pop-Location
This works by checking if the string starts with a quote-enclosed string (escaped quotes should not need to be handled within filepaths), and if so gets the first match from the $Matches object. $Matches is an automatic variable which is populated whenever you get a $True result using the [-match operator][1]. With the command path extracted, we use Split-Path to get the parent container relative to the filepath.
Then use Push-Location to change directories. Push-Location works like Set-Location (aliased to cd) except it tracks the directories you leave and enter as a stack. Its sibling cmdlet Pop-Location is used further on to return to the previous location.
Finally, we use Invoke-Expression to run your command. After this completes use Pop-Location to return to the previous directory. Keep the following in mind:
You should take note that the use of Invoke-Expression is often implemented insecurely, and so you should consider heeding the warning on the documentation I've linked to and consider parameterizing your command if your $commandString is actually populated from a generated file, provided by a parameter, or another other outside source.
Note: You mentioned this in your question:
The second line (note there is no exe)
Windows doesn't care if you omit the extension for executable types when executing them. You can run AzInfoProtection.exe with or without the .exe at the end. So unless I'm missing something this detail doesn't have any bearing on how this code works.
To run the string you can pipe it to cmd to run it using:
$commandString | cmd

Process files dragged to BAT file in PowerShell

I trying to create a script that will convert Markdown files that are dropped on a processing script.
To accomplish this, I created a (process.bat) that would pass the names of the dropped files to a PowerShell script:
powershell.exe -NoProfile -File "./process.ps1" -Document %*
#pause
The PowerShell file (process.ps1) would process each file individually:
[parameter(Mandatory=$true)]
[String[]]$Document
Write-Host $args[1]
$Document | ForEach-Object {
Write-Host "Document: $_"
# convert Markdown to Html
pandoc -o ($_ -Replace '.md', '.html') -f markdown -t html $_
}
When I drop two files on the batch file:
C:\Users\XXX\Documents\WindowsPowerShell\Scripts\Markdown>powershell.exe -NoProfile -File "./process.ps1" -Document "C:\Users\XXX\Documents\WindowsPowerShell\Scripts\Markdown\FOO.md"
"C:\Users\XXX\Documents\WindowsPowerShell\Scripts\Markdown\BAR.md"
C:\Users\XXX\Documents\WindowsPowerShell\Scripts\Markdown\BAR.md
Document:
Press any key to continue . . .
The documents are not being processed.
What's the recommended approach to passing the batch file's file list %* to PowerShell?
When powershell.exe, the PowerShell CLI, is called from the outside with the -File parameter, it doesn't support arrays - only individual arguments.[1]
(Additionally, you neglected to wrap your parameter definition in a param(...) block, which effectively caused it be ignored.)
The simplest solution is to define your parameter with the ValueFromRemainingArguments option so that it automatically collects all positional arguments in the parameter variable:
param(
[Parameter(Mandatory, ValueFromRemainingArguments)]
[String[]] $Document
)
Then invoke your script via the PowerShell CLI without -Document:
powershell.exe -NoProfile -File "./process.ps1" %*
As an alternative to using a helper batch file, you can:
define a shortcut file (*.lnk) that explicitly invokes your PowerShell script as powershell.exe -File \path\to\your\script.ps1 (without additional arguments)
and then use that as the drop target.
Note: The reason that you cannot use a PowerShell script (*.ps1) directly as a drop target is that PowerShell script files aren't directly executable - instead, opening (double-clicking) a *.ps1 file opens it for editing.
You'd then need to add pause (or something similar, such as Read-Host -Prompt 'Press Enter to exit.') to your PowerShell script, to prevent it from closing the window instantly on finishing.
Alternatively, leave the script as-is and use -NoExit (placed before -File) to keep the PowerShell session open.
[1] The same applies to the PowerShell Core CLI, pwsh.

PowerShell scriptBlock wraps the script when trying to write scriptblock to file

I am trying to create a PowerShell script by another PowerShell script. I have something like:
>
$scriptBlock = {
write-host "this is the body of the script that I want to add to another PS script"
if($true){
write-host "so that I can execute this automatically created script somewhere "
}
}
$scriptBlock | Out-String -Width 4096 | Out-File "c:\test.ps1"
AtRunUnattended.exe "$($Dict.Get_Item("MASTER_DIRECTORY_PATH"))\MEDIA_FILE\" /S /noreboot /L logfile="%TEMP%\AT $($Dict.Get_Item("PRODUCT_NAME")) V8.4.log" altsource="C:\temp\baf\mediafile"
However, when I have some long single-line scripts like the one shown above, it gets automatically wrapped so the out-put .ps1 file would not work as if I had invoked the script directly from the parent script.
So in the .ps1 file that the parent .ps1 script created, the code then looks like this:
ElseIf($str.StartsWith("DRIVER_DATE=")){$str = "DRIVER_DATE=$(Get-Date
-f MM-dd-yyyy))"}
which will not run properly if it is run.
So does anybody know how to text-format scriptblocks so that they can be properly written to another child script file for further execution?
I did some research and I think that it might have something to do with PS's internal text buffer width or something. I tried other out-file methods as well like [System.IO.StreamWriter], however all of them look the same--wrapped and limited to a certain width per line.
Please help me, thank you!
The entire purpose of this thing is to generate some scripts automatically and remotely execute these created scripts on other machines.
Use the -Width parameter with the Out-File cmdlet as follows:
Out-File -FilePath "c:\test.ps1" -Width 4096
the variable will not be expanded in single quote. May be you need a sample like this:
$scripts=#'
$date=get-date
"Hello,now is $date"
'#
$scripts | Out-File second.ps1
./second.ps1
Output is:
Hello,now is 09/23/2013 23:41:18

PowerShell: Run command from script's directory

I have a PowerShell script that does some stuff using the script’s current directory. So when inside that directory, running .\script.ps1 works correctly.
Now I want to call that script from a different directory without changing the referencing directory of the script. So I want to call ..\..\dir\script.ps1 and still want that script to behave as it was called from inside its directory.
How do I do that, or how do I modify a script so it can run from any directory?
Do you mean you want the script's own path so you can reference a file next to the script? Try this:
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
Write-host "My directory is $dir"
You can get a lot of info from $MyInvocation and its properties.
If you want to reference a file in the current working directory, you can use Resolve-Path or Get-ChildItem:
$filepath = Resolve-Path "somefile.txt"
EDIT (based on comment from OP):
# temporarily change to the correct folder
Push-Location $dir
# do stuff, call ant, etc
# now back to previous directory
Pop-Location
There's probably other ways of achieving something similar using Invoke-Command as well.
There are answers with big number of votes, but when I read your question, I thought you wanted to know the directory where the script is, not that where the script is running. You can get the information with powershell's auto variables
$PSScriptRoot # the directory where the script exists, not the
# target directory the script is running in
$PSCommandPath # the full path of the script
For example, I have a $profile script that finds a Visual Studio solution file and starts it. I wanted to store the full path, once a solution file is started. But I wanted to save the file where the original script exists. So I used $PsScriptRoot.
If you're calling native apps, you need to worry about [Environment]::CurrentDirectory not about PowerShell's $PWD current directory. For various reasons, PowerShell does not set the process' current working directory when you Set-Location or Push-Location, so you need to make sure you do so if you're running applications (or cmdlets) that expect it to be set.
In a script, you can do this:
$CWD = [Environment]::CurrentDirectory
Push-Location $MyInvocation.MyCommand.Path
[Environment]::CurrentDirectory = $PWD
## Your script code calling a native executable
Pop-Location
# Consider whether you really want to set it back:
# What if another runspace has set it in-between calls?
[Environment]::CurrentDirectory = $CWD
There's no foolproof alternative to this. Many of us put a line in our prompt function to set [Environment]::CurrentDirectory ... but that doesn't help you when you're changing the location within a script.
Two notes about the reason why this is not set by PowerShell automatically:
PowerShell can be multi-threaded. You can have multiple Runspaces (see RunspacePool, and the PSThreadJob module) running simultaneously withinin a single process. Each runspace has it's own $PWD present working directory, but there's only one process, and only one Environment.
Even when you're single-threaded, $PWD isn't always a legal CurrentDirectory (you might CD into the registry provider for instance).
If you want to put it into your prompt (which would only run in the main runspace, single-threaded), you need to use:
[Environment]::CurrentDirectory = Get-Location -PSProvider FileSystem
This would work fine.
Push-Location $PSScriptRoot
Write-Host CurrentDirectory $CurDir
I often used the following code to import a module which sit under the same directory as the running script. It will first get the directory from which powershell is running
$currentPath=Split-Path ((Get-Variable
MyInvocation -Scope
0).Value).MyCommand.Path
import-module "$currentPath\sqlps.ps1"
I made a one-liner out of #JohnL's solution:
$MyInvocation.MyCommand.Path | Split-Path | Push-Location
Well I was looking for solution for this for a while, without any scripts just from CLI. This is how I do it xD:
Navigate to folder from which you want to run script (important thing is that you have tab completions)
..\..\dir
Now surround location with double quotes, and inside them add cd, so we could invoke another instance of powershell.
"cd ..\..\dir"
Add another command to run script separated by ;, with is a command separator in powershell
"cd ..\..\dir\; script.ps1"
Finally Run it with another instance of powershell
start powershell "cd..\..\dir\; script.ps1"
This will open new powershell window, go to ..\..\dir, run script.ps1 and close window.
Note that ";" just separates commands, like you typed them one by one, if first fails second will run and next after, and next after... If you wanna keep new powershell window open you add -noexit in passed command . Note that I first navigate to desired folder so I could use tab completions (you couldn't in double quotes).
start powershell "-noexit cd..\..\dir\; script.ps1"
Use double quotes "" so you could pass directories with spaces in names e.g.,
start powershell "-noexit cd '..\..\my dir'; script.ps1"