Win-PS2EXE - Respect the location of the resulting executable - powershell

Take these settings for the program Win-PS2EXE:
This is so that the console will show when the exe file is clicked on.
And this code:
$inf_file = "$PSScriptRoot\setup-files\install.inf"
write-host """$inf_file"""
timeout 10
Let us say that the path of the new executable is W:\Apps\Install Scheme.exe
Which means the $inf_file is here W:\Apps\setup-files\install.inf
When I click the converted exe file I get this.
Is there any way to get the correct path of W:\Apps\setup-files\install.inf so that the executable recognises the location of itself when clicked.
I thought that $PSScriptRoot would work.
I'm lost as to how to get around this as the exe file will eventually depend on knowing its location.

Here's the code that can accomplish that.
Function Get-PSScriptPath {
<#
.SYNOPSIS
Returns the current filepath of the .ps1 or compiled .exe with Win-PS2EXE.
.DESCRIPTION
This will return the path of the file. This will work when the .ps1 file is
converted with Win-PS2EXE
.NOTES
Author: Ste
Date Created: 2021.05.03
Tested with PowerShell 5.1 and 7.1.
Posted here: https://stackoverflow.com/q/60121313/8262102
.PARAMETER None
NA
.INPUTS
None. You cannot pipe objects to Get-PSScriptPath.
.OUTPUTS
Returns the current filepath of the .ps1 or compiled .exe with Win-PS2EXE.
.EXAMPLE (When run from a .ps1 file)
PS> Get-PSScriptPath
PS> C:\Users\Desktop\temp.ps1
.EXAMPLE (When run from a compiled .exe file with Win-PS2EXE.
PS> Get-PSScriptPath
PS> C:\Users\Desktop\temp.exe
#>
if ([System.IO.Path]::GetExtension($PSCommandPath) -eq '.ps1') {
$psScriptPath = $PSCommandPath
} else {
# This enables the script to be compiles and get the directory of it.
$psScriptPath = [System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName
}
return $psScriptPath
}
Get-PSScriptPath

To offer a pragmatic, concise alternative (PSv3+) that always reports the script path as a full path:
One-liner:
$scriptDir = if (-not $PSScriptRoot) { Split-Path -Parent (Convert-Path ([environment]::GetCommandLineArgs()[0])) } else { $PSScriptRoot }
Annotated form:
$scriptDir = if (-not $PSScriptRoot) { # $PSScriptRoot not defined?
# Get the path of the executable *as invoked*, via
# [environment]::GetCommandLineArgs()[0],
# resolve it to a full path with Convert-Path, then get its directory path
Split-Path -Parent (Convert-Path ([environment]::GetCommandLineArgs()[0]))
}
else {
# Use the automatic variable.
$PSScriptRoot
}

Related

touch Function in PowerShell

I recently added a touch function in PowerShell profile file
PS> notepad $profile
function touch {Set-Content -Path ($args[0]) -Value ($null)}
Saved it and ran a test for
touch myfile.txt
error returned:
touch : The term 'touch' 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.
At line:1 char:1
+ touch myfile
+ ~~~~~
+ CategoryInfo : ObjectNotFound: (touch:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
With PowerShell there are naming conventions for functions. It is higly recommended to stick with that if only to stop getting warnings about it if you put those functions in a module and import that.
A good read about naming converntion can be found here.
Having said that, Powershell DOES offer you the feature of Aliasing and that is what you can see here in the function below.
As Jeroen Mostert and the others have already explained, a Touch function is NOT about destroying the content, but only to set the LastWriteTine property to the current date.
This function alows you to specify a date yourself in parameter NewDate, but if you leave it out it will default to the current date and time.
function Set-FileDate {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline = $true, Mandatory = $true, Position = 0)]
[string[]]$Path,
[Parameter(Mandatory = $false, Position = 1)]
[datetime]$NewDate = (Get-Date),
[switch]$Force
)
Get-Item $Path -Force:$Force | ForEach-Object { $_.LastWriteTime = $NewDate }
}
Set-Alias Touch Set-FileDate -Description "Updates the LastWriteTime for the file(s)"
Now, the function has a name PowerShell won't object to, but by using the Set-Alias you can reference it in your code by calling it touch
Here is a version that creates a new file if it does not exist or updates the timestamp if it does exist.
Function Touch-File
{
$file = $args[0]
if($file -eq $null) {
throw "No filename supplied"
}
if(Test-Path $file)
{
(Get-ChildItem $file).LastWriteTime = Get-Date
}
else
{
echo $null > $file
}
}
If you have a set of your own custom functions stored in a .ps1 file, you must first import them before you can use them, e.g.
Import-module .\MyFunctions.ps1 -Force
To avoid confusion:
If you have placed your function definition in your $PROFILE file, it will be available in future PowerShell sessions - unless you run . $PROFILE in the current session to reload the updated profile.
Also note that loading of $PROFILE (all profiles) can be suppressed by starting a session with powershell.exe -NoProfile (Windows PowerShell) / pwsh -NoProfile (PowerShell (Core)).
As Jeroen Mostert points out in a comment on the question, naming your function touch is problematic, because your function unconditionally truncates an existing target file (discards its content), whereas the standard touch utility on Unix-like platforms leaves the content of existing files alone and only updates their last-write (and last-access) timestamps.
See this answer for more information about the touch utility and how to implement equivalent behavior in PowerShell.

Visual studio Build Task Issue for PowerShell inline task - Azure

I am running a vsts build inline PowerShell script task to create package for Azure cloud service. It works fine and create package file from my local machine, but when I try to run from VSTS PowerShell inline task it gives error :
##[error]Cannot find path ‘D:\a_tasks\InlinePowershell_31f040e5-e040-4336-878a-59a493355534\1.1.6\ServiceConfiguration.Cloud.Test.cscfg’ because it does not exist.
Here is my PowerShell inline script below, It fails on the following line:
Copy-Item $serviceConfigurationPath $packageOutDir
I really appreciate your help on this.
Thanks,
# This is the VSTS repository path
$workingDirectory = “$/DevCodeBase/ToolDevBranch1.33”
$webProjectName = “WebRole1”
$cloudProjectName = ‘ProjAzureDeployment’
$evv =’Test’
$cppack = ‘C:\Program Files\Microsoft SDKs\Azure\.NET SDK\v2.9\bin\cspack.exe’
$solutionDir = [string]::Format(“{0}”, $workingDirectory)
$webDir = [string]::Format(“{0}\{1}”, $workingDirectory, $webProjectName)
$packageOutDir = [string]::Format(“{0}\{1}”, $workingDirectory, $cloudProjectName)
$rolePropertyFile = [string]::Format(“{0}\{1}\{2}”, $workingDirectory, $cloudProjectName, “roleproperties.txt”)
# Create Role Properties File – This property file specifies the .Net framework against which webrole is going to run.
New-Item $rolePropertyFile -Type file -Force -Value “TargetFrameWorkVersion=v4.5” | Out-Null
New-Item $packageOutDir -Type directory -Force | Out-Null
# CSPack command Definition
$serviceDefinitionPath = [string]::Format(“{0}\{1}\ServiceDefinition.csdef”, $solutionDir, $cloudProjectName)
if ($evv -eq “Test”){
$serviceConfigurationPath = “ServiceConfiguration.Cloud.Test.cscfg”
}
else
{
$serviceConfigurationPath = [string]::Format(“{0}\{1}\ServiceConfiguration.Cloud.cscfg”, $solutionDir, $cloudProjectName)
}
$serviceRole = [string]::Format(“/role:{0};{1}”, $webProjectName, $webDir)
$rolePropertiesFile = [string]::Format(“/rolePropertiesFile:{0};{1}”, $webProjectName, $rolePropertyFile)
$sites = [string]::Format(“/sites:{0};Web;{1}”, $webProjectName, $webDir)
$packageOutput = [string]::Format(“/out:{0}\{1}.cspkg”, $packageOutDir, $cloudProjectName)
# $packageOutput = [string]::Format(“{0}\{1}.cspkg”, $packageOutDir, $cloudProjectName)
Write-Host $packageOutput
Write-Host $serviceConfigurationPath
# Build CSPKG file
& “C:\Program Files\Microsoft SDKs\Azure\.NET SDK\v2.9\bin\cspack.exe” $serviceDefinitionPath $serviceRole $rolePropertiesFile $sites $packageOutput /useCtpPackageFormat | Out-Null
Write-Host $serviceDefinitionPath
Write-Host $serviceRole
Write-Host $rolePropertiesFile
Write-Host $sites
Write-Host $packageOutput
Write-Host ‘before copy’
# Copy configuration file
Copy-Item $serviceConfigurationPath $packageOutDir
# Remove Role Properties File
Remove-Item -Path $rolePropertyFile -Force | Out-Null
In the VSTS task you'll have to specify an absolute path, otherwise the script will look in the temporary directory created for your inline powershell script.
For instance, you could supply the path to the file as a parameter like
-filepath "$(System.DefaultWorkingDirectory)\Solution\config.json"
(For a list of the variables you can use, have a peek here)
If you want to keep using a relative path, you can move to a file based (ie non-inline) script and use a relative path to that.

Can a function know from where it is exectuted?

$MyInvocation can be used to identify where the source of a script is located.
How can a function do the same?
function whereami {
Write-Host $MyInvocation.MyCommand.Path # produces nothing
}
Write-Host $MyInvocation.MyCommand.Path
whereami
The C programmer would use __FILE__.
There is $PSCommandPath which returns the full path to the script being executed.
Note that the script must be saved as a file before this will work in the ISE.
function whereami {
$PSCommandPath
}
Write-Host $MyInvocation.MyCommand.Path
whereami
Returns:
PS C:\Users\> C:\Users\somescript.ps1
C:\Users\somescript.ps1
C:\Users\somescript.ps1

PowerShell Test-Path does not work for argument-parameters

During my analysis why my script isn't working I could find some solutions that solves some basic parts (also with the help of Stackoverflow), but there is still an issue for which I cannot find a solution.
When I create a temp-directory-variable $PARAM_TEMP and check this with Test-Path everything is working fine (wether the directory exists or not). But on the next part, when I use the same technique for the arguments-parameter $PARAM_DESTINATION the statement-result is wrong (I already found the hint for adding single-quotes - after this, at least the scripts runs through without errors).
When I test my test-output in console in the Windows-Explorer, it finds the directory (thus it definitely exists)
here's the content of my script BeforeInstallationScript2.ps1:
#
# BeforeInstallationScript2.ps1
#
param(
[Parameter(
Mandatory=$true,
Position=0,
HelpMessage='Path to targetDir')]
[string] $PARAM_DESTINATION
)
"1st Argument: $PARAM_DESTINATION"
# create temporary directory if not exists
$PARAM_TEMP=$env:TEMP+"\MyApp"
"temporary Directory: $PARAM_TEMP"
if(!(Test-Path -Path "$PARAM_TEMP" )){
"temp-dir does NOT exist and will be created"
New-Item -ItemType directory -Path $PARAM_TEMP
} else {
"temp-dir exist"
}
# create Timestamp-variable for saving configs
$a = Get-Date
$DATETIME= "" + $a.Year + $a.Month + $a.Day + $a.Hour + $a.Minute
"Timestamp: $DATETIME"
# if there exists already a myApp-Installation, copy config-files
"Parameter-Path=: $PARAM_DESTINATION"
"exists? = " + (Test-Path "'$PARAM_DESTINATION'" )
if((Test-Path -Path "'$PARAM_DESTINATION'" )) {
"param-path exists"
if((Test-Path -Path "'$PARAM_DESTINATION\configuration\MyApp.conf'" )) {
"copy file to $PARAM_TEMP\$DATETIME-MyApp.conf"
Copy-Item "$PARAM_DESTINATION\configuration\MyApp.conf" "$PARAM_TEMP\$DATETIME-MyApp.conf"
}
} else {
"not existing, no files to copy/save"
}
I this script in powershell get the output as follows:
PS X:\MyApp-Setup> C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe -Version 2.0 -NoProfile -NonInteractive -InputFormat None -ExecutionPolicy Bypass -File ".\BeforeInstallationScript2.ps1" "C:\Program Files (x86)\Internet Explorer"
1st Argument: C:\Program Files (x86)\Internet Explorer
temporary Directory: C:\Users\ixmid\AppData\Local\Temp\MyApp
temp-dir does NOT exist and will be created
Verzeichnis: C:\Users\USER\AppData\Local\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 16.11.2016 11:01 MyApp
Timestamp: 20161116111
Parameter-Path=: C:\Program Files (x86)\Internet Explorer
exists? = False
not existing, no files to copy/save
PS X:\MyApp-Setup>
As you can see, first Test-Path works fine, and creates the missing directory. But at the second part it doesn't work fine.
Any suggestions, why the second (and third) Test-Path-statements are working wrong?
For completion: second output of executing the script (when MyApp-directory now exists) looks as follows:
1st Argument: C:\Program Files (x86)\Internet Explorer
temporary Directory: C:\Users\USER\AppData\Local\Temp\MyApp
temp-dir exist
Timestamp: 201611161113
Parameter-Path=: C:\Program Files (x86)\Internet Explorer
exists? = False
not existing, no files to copy/save
You use both " quotes and ' quotes. If you want variables, $PARAM_DESTINATION in this case, to expand you need to use double quotes only. So: "$PARAM_DESTINATION".
Read up more on double and single quotes
http://windowsitpro.com/blog/single-quotes-vs-double-quotes-powershell-whats-difference

How can I get the current PowerShell executing file?

Note: PowerShell 1.0
I'd like to get the current executing PowerShell file name. That is, if I start my session like this:
powershell.exe .\myfile.ps1
I'd like to get the string ".\myfile.ps1" (or something like that). EDIT: "myfile.ps1" is preferable.
Any ideas?
I've tried to summarize the various answers here, updated for PowerShell 5:
If you're only using PowerShell 3 or higher, use $PSCommandPath
If want compatibility with older versions, insert the shim:
if ($PSCommandPath -eq $null) { function GetPSCommandPath() { return $MyInvocation.PSCommandPath; } $PSCommandPath = GetPSCommandPath }
This adds $PSCommandPath if it doesn't already exist.
The shim code can be executed anywhere (top-level or inside a function), though $PSCommandPath variable is subject to normal scoping rules (eg, if you put the shim in a function, the variable is scoped to that function only).
Details
There's 4 different methods used in various answers, so I wrote this script to demonstrate each (plus $PSCommandPath):
function PSCommandPath() { return $PSCommandPath }
function ScriptName() { return $MyInvocation.ScriptName }
function MyCommandName() { return $MyInvocation.MyCommand.Name }
function MyCommandDefinition() {
# Begin of MyCommandDefinition()
# Note: ouput of this script shows the contents of this function, not the execution result
return $MyInvocation.MyCommand.Definition
# End of MyCommandDefinition()
}
function MyInvocationPSCommandPath() { return $MyInvocation.PSCommandPath }
Write-Host ""
Write-Host "PSVersion: $($PSVersionTable.PSVersion)"
Write-Host ""
Write-Host "`$PSCommandPath:"
Write-Host " * Direct: $PSCommandPath"
Write-Host " * Function: $(PSCommandPath)"
Write-Host ""
Write-Host "`$MyInvocation.ScriptName:"
Write-Host " * Direct: $($MyInvocation.ScriptName)"
Write-Host " * Function: $(ScriptName)"
Write-Host ""
Write-Host "`$MyInvocation.MyCommand.Name:"
Write-Host " * Direct: $($MyInvocation.MyCommand.Name)"
Write-Host " * Function: $(MyCommandName)"
Write-Host ""
Write-Host "`$MyInvocation.MyCommand.Definition:"
Write-Host " * Direct: $($MyInvocation.MyCommand.Definition)"
Write-Host " * Function: $(MyCommandDefinition)"
Write-Host ""
Write-Host "`$MyInvocation.PSCommandPath:"
Write-Host " * Direct: $($MyInvocation.PSCommandPath)"
Write-Host " * Function: $(MyInvocationPSCommandPath)"
Write-Host ""
Output:
PS C:\> .\Test\test.ps1
PSVersion: 5.1.19035.1
$PSCommandPath:
* Direct: C:\Test\test.ps1
* Function: C:\Test\test.ps1
$MyInvocation.ScriptName:
* Direct:
* Function: C:\Test\test.ps1
$MyInvocation.MyCommand.Name:
* Direct: test.ps1
* Function: MyCommandName
$MyInvocation.MyCommand.Definition:
* Direct: C:\Test\test.ps1
* Function:
# Begin of MyCommandDefinition()
# Note this is the contents of the MyCommandDefinition() function, not the execution results
return $MyInvocation.MyCommand.Definition;
# End of MyCommandDefinition()
$MyInvocation.PSCommandPath:
* Direct:
* Function: C:\Test\test.ps1
Notes:
Executed from C:\, but actual script is C:\Test\test.ps1.
No method tells you the passed invocation path (.\Test\test.ps1)
$PSCommandPath is the only reliable way, but was introduced in PowerShell 3
For versions prior to 3, no single method works both inside and outside of a function
While the current Answer is right in most cases, there are certain situations that it will not give you the correct answer. If you use inside your script functions then:
$MyInvocation.MyCommand.Name
Returns the name of the function instead name of the name of the script.
function test {
$MyInvocation.MyCommand.Name
}
Will give you "test" no matter how your script is named.
The right command for getting the script name is always
$MyInvocation.ScriptName
this returns the full path of the script you are executing. If you need just the script filename than this code should help you:
split-path $MyInvocation.PSCommandPath -Leaf
If you only want the filename (not the full path) use this:
$ScriptName = $MyInvocation.MyCommand.Name
Try the following
$path = $MyInvocation.MyCommand.Definition
This may not give you the actual path typed in but it will give you a valid path to the file.
beware:
Unlike the $PSScriptRoot and $PSCommandPath automatic variables, the
PSScriptRoot and PSCommandPath properties of the $MyInvocation automatic
variable contain information about the invoker or calling script, not the
current script.
e.g.
PS C:\Users\S_ms\OneDrive\Documents> C:\Users\SP_ms\OneDrive\Documents\DPM ...
=!C:\Users\S_ms\OneDrive\Documents\DPM.ps1
...where DPM.ps1 contains
Write-Host ("="+($MyInvocation.PSCommandPath)+"!"+$PSCommandPath)
If you are looking for the current directory in which the script is being executed, you can try this one:
$fullPathIncFileName = $MyInvocation.MyCommand.Definition
$currentScriptName = $MyInvocation.MyCommand.Name
$currentExecutingPath = $fullPathIncFileName.Replace($currentScriptName, "")
Write-Host $currentExecutingPath
As noted in previous responses, using "$MyInvocation" is subject to scoping issues and doesn't necessarily provide consistent data (return value vs. direct access value). I've found that the "cleanest" (most consistent) method for getting script info like script path, name, parms, command line, etc. regardless of scope (in main or subsequent/nested function calls) is to use "Get-Variable" on "MyInvocation"...
# Get the MyInvocation variable at script level
# Can be done anywhere within a script
$ScriptInvocation = (Get-Variable MyInvocation -Scope Script).Value
# Get the full path to the script
$ScriptPath = $ScriptInvocation.MyCommand.Path
# Get the directory of the script
$ScriptDirectory = Split-Path $ScriptPath
# Get the script name
# Yes, could get via Split-Path, but this is "simpler" since this is the default return value
$ScriptName = $ScriptInvocation.MyCommand.Name
# Get the invocation path (relative to $PWD)
# #GregMac, this addresses your second point
$InvocationPath = ScriptInvocation.InvocationName
So, you can get the same info as $PSCommandPath, but a whole lot more in the deal. Not sure, but it looks like "Get-Variable" was not available until PS3 so not a lot of help for really old (not updated) systems.
There are also some interesting aspects when using "-Scope" as you can backtrack to get the names, etc. of the calling function(s). 0=current, 1=parent, etc.
Hope this is somewhat helpful.
Ref, https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-variable
I would argue that there is a better method, by setting the scope of the variable $MyInvocation.MyCommand.Path:
ex> $script:MyInvocation.MyCommand.Name
This method works in all circumstances of invocation:
EX:
Somescript.ps1
function printme () {
"In function:"
( "MyInvocation.ScriptName: " + [string]($MyInvocation.ScriptName) )
( "script:MyInvocation.MyCommand.Name: " + [string]($script:MyInvocation.MyCommand.Name) )
( "MyInvocation.MyCommand.Name: " + [string]($MyInvocation.MyCommand.Name) )
}
"Main:"
( "MyInvocation.ScriptName: " + [string]($MyInvocation.ScriptName) )
( "script:MyInvocation.MyCommand.Name: " + [string]($script:MyInvocation.MyCommand.Name) )
( "MyInvocation.MyCommand.Name: " + [string]($MyInvocation.MyCommand.Name) )
" "
printme
exit
OUTPUT:
PS> powershell C:\temp\test.ps1
Main:
MyInvocation.ScriptName:
script:MyInvocation.MyCommand.Name: test.ps1
MyInvocation.MyCommand.Name: test.ps1
In function:
MyInvocation.ScriptName: C:\temp\test.ps1
script:MyInvocation.MyCommand.Name: test.ps1
MyInvocation.MyCommand.Name: printme
Notice how the above accepted answer does NOT return a value when called from Main. Also, note that the above accepted answer returns the full path when the question requested the script name only. The scoped variable works in all places.
Also, if you did want the full path, then you would just call:
$script:MyInvocation.MyCommand.Path
A short demonstration of #gregmac's (excellent and detailed) answer, which essentially recommends $PSCommandPath as the only reliable command to return the currently running script where Powershell 3.0 and above is used.
Here I show returning either the full path or just the file name.
Test.ps1:
'Direct:'
$PSCommandPath # Full Path
Split-Path -Path $PSCommandPath -Leaf # File Name only
function main () {
''
'Within a function:'
$PSCommandPath
Split-Path -Path $PSCommandPath -Leaf
}
main
Output:
PS> .\Test.ps1
Direct:
C:\Users\John\Documents\Sda\Code\Windows\PowerShell\Apps\xBankStatementRename\Test.ps1
Test.ps1
Within a function:
C:\Users\John\Documents\Sda\Code\Windows\PowerShell\Apps\xBankStatementRename\Test.ps1
Test.ps1
Did some testing with the following script, on both PS 2 and PS 4 and had the same result. I hope this helps people.
$PSVersionTable.PSVersion
function PSscript {
$PSscript = Get-Item $MyInvocation.ScriptName
Return $PSscript
}
""
$PSscript = PSscript
$PSscript.FullName
$PSscript.Name
$PSscript.BaseName
$PSscript.Extension
$PSscript.DirectoryName
""
$PSscript = Get-Item $MyInvocation.InvocationName
$PSscript.FullName
$PSscript.Name
$PSscript.BaseName
$PSscript.Extension
$PSscript.DirectoryName
Results -
Major Minor Build Revision
----- ----- ----- --------
4 0 -1 -1
C:\PSscripts\Untitled1.ps1
Untitled1.ps1
Untitled1
.ps1
C:\PSscripts
C:\PSscripts\Untitled1.ps1
Untitled1.ps1
Untitled1
.ps1
C:\PSscripts
This can works on most powershell versions:
(& { $MyInvocation.ScriptName; })
This can work for Scheduled Job
Get-ScheduledJob |? Name -Match 'JOBNAMETAG' |% Command