Powershell: stripping a file name sometimes doesn't work - powershell

I have a Powershell script that is called from the command line.
script.ps1 "\\testfolder" "testinput" "xml" "xml2html.xsl" "testfile" "css"
The script uses these command line arguments:
param([string]$publish_folder, [string]$input_filename, [string]$input_ext, [string]$transformation_filename, [string]$output_filename, [string]$output_ext)
$input_filename and $output_filename may be a full path+filename, the filename only or the filename without extension.
$inputFileNameOnly = [System.IO.Path]::GetFileNameWithoutExtension($input_filename)
$inputPath=$publish_folder+"\"+$inputFileNameOnly+"."+$input_ext
$outputFileNameOnly = [System.IO.Path]::GetFileNameWithoutExtension($output_filename)
$outputPath=$publish_folder+"\"+$outputFileNameOnly+"."+$output_ext
When I run this locally, it works. Output path:
\\testfolder\testfile.css
When I run the same script in an AWS instance, it fails. $inputpath is calculated correctly, but Output path becomes:
\\testfolder\.
so both $output_filename and $output_ext are empty.
The paths are longer than \\testfolder\, but not long enough to cause trouble (about 150 characters). Total length of the arguments doesn't seem to be a problem either.
What could be causing this problem?

$outputPath="$publish_folder+"\"+
Looks like you broke your concatenation here. Double-quote before $publish_folder.
Edit:
Can also be written like this if you don't want to concatenate (+)
$outputPath="$publish_folder\$outputFileNameOnly.$output_ext"
Anything in double-quotes should be expanded correctly.

I set up your script as a function and passed those parameters.
function rename {
param(
[string]$publish_folder,
[string]$input_filename,
[string]$input_ext,
[string]$transformation_filename,
[string]$output_filename,
[string]$output_ext
)
$inputFileNameOnly = [System.IO.Path]::GetFileNameWithoutExtension($input_filename)
$inputPath = $publish_folder+"\"+$inputFileNameOnly+"."+$input_ext
$outputFileNameOnly = [System.IO.Path]::GetFileNameWithoutExtension($output_filename)
$outputPath = $publish_folder+"\"+$outputFileNameOnly+"."+$output_ext
return $outputPath
}
Output is this...
\\\\testfolder\\\\testfile.css
Try passing $publish_folder without ending backslash
Edit: Sorry about formatting. Need some forum practice. =)

With some more testing, we found the cause of the problem.
$outputFileNameOnly = [System.IO.Path]::GetFileNameWithoutExtension($output_filename)
GetFileNameWithoutExtension fails (in the AWS instance) when $output_filename does not contain an extension. On my local machine this worked correctly.
So we added an extension to the command line argument and the script works correctly.

Related

How to include OS command with Substitute function in Openedge?

Im trying to create an PDF via OS-Command in OpenEdge but I hit an error when I run the script.
*Error : The command "C: \ Program" is either misspelled or could not be found
It works perfectly :
os-command (' "C:\Program Files (x86)\wkhtmltopdf\wkhtmltopdf.exe"
"V:\V11\WEB\PDF\Name_01.03.2021_14.09.30_da.html"
"V:\V11\WEB\PDF\Name_01.03.2021_14.09.30_da.pdf" ').
However, when I Include the command in script and run it then I encounter an error.
This one doesnt work :
define variable cmdcommand as char no-undo. cmdcommand = SUBSTITUTE
(' "C:\Program Files (x86)\wkhtmltopdf\wkhtmltopdf.exe"
"V:\V11\WEB\PDF\Name_&1_&2_&3.html"
"V:\V11\WEB\PDF\Name_&1_&2_&3.pdf" ', "01.03.2021", "14.09.30", "da").
os-command value(cmdcommand).
What did I miss here? Can anyone help?
After having fought with os-command for quite some time to get normal errors and output returned, if you are only targeting Windows then it you may find it easier to use the .Net System.Diagnostics.Process class.
To get you started:
define variable oProcess as System.Diagnostics.Process no-undo.
define variable oInfo as System.Diagnostics.ProcessStartInfo no-undo.
oProcess = new System.Diagnostics.Process().
assign
oInfo = oProcess:StartInfo
oInfo:FileName = "C:~\Program Files (x86)~\winmerge~\winmergeu.exe".
oInfo:WorkingDirectory = "session:temp-directory
oInfo:Arguments = substitute(
"&1 &2",
quoter( "file1.txt" ),
quoter( "file2.txt" )
)
.
oProcess:Start().
oProcess:WaitForExit().
Other useful properties of the ProcessStartInfo class include:
CreateNoWindow
UseShellExecute
RedirectStandardError
RedirectStandardOutput
In your second sample, there's no value in using SUBSTITUTE, as your're not using place holders (&1, &2, ...). What you're doing is basically a straight forward string assignment.
The resulting string looks like this:
"C:\Program Files (x86)\wkhtmltopdf\wkhtmltopdf.exe""V:\V11\WEB\PDF\Name_01.03.2021_14.09.30_da.html""V:\V11\WEB\PDF\Name_01.03.2021_14.09.30_da.pdf"
there's an extra space at the beginning
there's no space between the closing quote of the path to your exe and the first argument.
This here works for me:
define variable cmdcommand as char no-undo.
cmdcommand = SUBSTITUTE ('"c:\Program Files (x86)\WinMerge\winmergeu.exe" &1 &2',
"c:\temp\1.txt",
"c:\temp\2.txt").
OS-COMMAND silent value(cmdcommand).
Due to the use of the SUBSTITUTE function with place holders, this gives me a clean command with a space between the exe path and the first argument.
It works with or without the SILENT option.

How to use custom PowerShell functions?

Question about running a custom function in Powershell.
I'm on Windows 10 and I'd like to somehow print my monorepository's directory tree structure excluding node_modules. This is not supported out of the box but requires a custom function to be defined. I found one solution on StackOverflow (https://stackoverflow.com/a/43810460/9654273), which would enable using a command like:
tree -Exclude node_modules -Ascii > tree.txt
The problem is I don't know what to do with the provided source code :D The answer says "add to your $PROFILE, for instance", so I ran notepad $PROFILE in PowerShell, pasted the code snippet there, saved it and tried running the command. It didn't work because I did something wrong. According to the StackOverflow post's comments from anand_v.singh and mklement0 I was still running some other tree command, not the one I just attempted to define.
So how do I use a custom function in PowerShell? Starting point is that source code is on StackOverflow and I don't know where to paste it. Or do you know some other, easier way to print a directory tree on Windows 10 excluding node_modules?
I had the same problem with that function. The issue is the special characters in the hashtable at line 106:
$chars = #{
interior = ('├', '+')[$ndx]
last = ('└', '\')[$ndx] #'
hline = ('─', '-')[$ndx]
vline = ('│', '|')[$ndx]
space = ' '
}
I changed the special characters to ascii as follows:
$chars = #{
interior = ('+', '+')[$ndx]
last = ('\', '\')[$ndx] #'
hline = ('-', '-')[$ndx]
vline = ('|', '|')[$ndx]
space = ' '
}
The only downside is that you do not now have the option of using special graphics characters (the Ascii switch is still there, but does nothing). Maybe someone could tell us how to embed them properly.

Is it possible to dot source a string variable in PowerShell?

I know I can dot source a file:
. .\MyFunctions.ps1
But, I would like to dot source the commands in a string variable:
. $myFuctions
I see that this is possible:
.{$x=2}
And $x equals 2 after the script block is sourced.
But... .{$myFunctions} does not work.
I tried $myFunctions | Invoke-Expression, but it doesn't keep the source function in the current scope. The closest I have been able to come up with is to write the variable to a temporary file, dot source the file, and then remove the file.
Inevitably, someone will ask: "What are you trying to do?" So here is my use case:
I want to obfuscate some functions I intend to call from another script. I don't want to obfuscate the master script, just my additional functions. I have a user base that will need to adjust the master script to their network, directory structure and other local factors, but I don't want certain functions modified. I would also like to protect the source code. So, an alternate question would be: What are some good ways to protect PowerShell script code?
I started with the idea that PowerShell will execute a Base64-encoded string, but only when passed on the command line with -EncodedCommand.
I first wanted to dot source an encoded command, but I couldn't figure that out. I then decided that it would be "obfuscated" enough for my purposes if I converted by Base64 file into a decode string and dot sourced the value of the string variable. However, without writing the decoded source to a file, I cannot figure out how to dot source it.
It would satisfy my needs if I could Import-Module -EncodedCommand .\MyEncodedFile.dat
Actually, there is a way to achieve that and you were almost there.
First, as you already stated, the source or dot operator works either by providing a path (as string) or a script block. See also: . (source or dot operator).
So, when trying to dot-source a string variable, PowerShell thinks it is a path. But, thanks to the possibility of dot-sourcing script blocks, you could do the following:
# Make sure everything is properly escaped.
$MyFunctions = "function Test-DotSourcing { Write-Host `"Worked`" }"
. { Invoke-Expression $MyFunctions }
Test-DotSourcing
And you successfully dot-sourced your functions from a string variable!
Explanation:
With Invoke-Expression the string is evaluated and run in the child scope (script block).
Then with . the evaluated expressions are added to the current scope.
See also:
Invoke-Expression
About scopes
While #dwettstein's answer is a viable approach using Invoke-Expression to handle the fact that the function is stored as a string, there are other approaches that seem to achieve the same result below.
One thing I'm not crystal clear on is the scoping itself, Invoke-Expression doesn't create a new scope so there isn't exactly a need to dot source at that point...
#Define your function as a string
PS> $MyUselessFunction = "function Test-WriteSomething { 'It works!' }"
#Invoke-Expression would let you use the function
PS> Invoke-Expression $MyUselessFunction
PS> Test-WriteSomething
It works!
#Dot sourcing works fine if you use a script block
PS> $ScriptBlock = [ScriptBlock]::Create($MyUselessFunction)
PS> . $ScriptBlock
PS> Test-WriteSomething
It works!
#Or just create the function as a script block initially
PS> $MyUselessFunction = {function Test-WriteSomething { 'It works!' }}
PS> . $MyUselessFunction
PS> Test-WriteSomething
It works!
In other words, there are probably a myriad of ways to get something similar to what you want - some of them documented, and some of them divined from the existing documentation. If your functions are defined as strings, then Invoke-Expression might be needed, or you can convert them into script blocks and dot source them.
At this time it is not possible to dot source a string variable.
I stand corrected! . { Invoke-Expression $MyFunctions } definitely works!

PowerShell 3 ExpandString loses text

I'm finding that in PowerShell 3, ExpandString is truncating my template string and only giving the very beginning of it. This worked in PowerShell 2 without a hitch, so I'm not sure what's going wrong.
The goal is to insert the value of $theSetting into the template string. Note that I use a regex to escape quotes and graves so that PowerShell doesn't try to expand them, and that appears to be working fine.
PS > $theSetting = 'x'
PS > $template = '<?xml version="1.0" encoding="utf-8" ?><AppSettings><Setting value="${theSetting}"/></AppSettings>'
PS > $template = $template -replace "('|`"|``)", '`$1'
PS > $template
<?xml version=`"1.0`" encoding=`"utf-8`" ?><AppSettings><Setting value=`"${theSetting}`"/></AppSettings>
PS > $ExecutionContext.InvokeCommand.ExpandString($template)
<?xml version="
For some reason, it's cutting it off after the first double quote. I appreciate any help in identifying what changed between PowerShell 2 and 3.
As you might guess given the template text, I'm actually loading the template from a file and writing the contents back out, and this is being used for configuration files that are far more variable and complex than the sample template seen here. So something simple like a regex instead isn't really an option.
For the benefit of future readers, don't do this. Use a real templating engine (e.g., Mustache).
Powershell 3 changed the way it parses strings to where you no longer need to change the " to `". I'm not quite sure why it evaluates it as the end of a string though.
Like Keith said, if you remove the replace code you have you will be fine. You can also detect to see if you are running on a lower powershell version and do the replacement like this:
if ($PSVersionTable.PSVersion.Major -lt 3) { $var = $var -replace '"','`"' }
Hope this helps

'unexpected token' in PowerShell when fully pathing executable

Just trying to better understand why the second item below does not work. The first item is simple, the second seems clearer, the third seems unintuitive.
# My path includes pscp so this works.
pscp.exe -i $PRIVATE_KEY $file ${PROXY_USER}#${PROXY_HOST}:${PROXY_DIR}
# This does not work. I get unexpected token error. Why? What does that mean?
$PUTTY_PATH\pscp.exe -i $PRIVATE_KEY $file ${PROXY_USER}#${PROXY_HOST}:${PROXY_DIR}
# & is required to solve the problem.
& "$PUTTY_PATH\pscp.exe" -i $PRIVATE_KEY $file ${PROXY_USER}#${PROXY_HOST}:${PROXY_DIR}
That's because this is also considered a parse error:
"foo"\pscp.exe
Whereas this parses correctly as you have found:
"$PUTTY_PATH\pscp.exe"
That resolves to a valid string but as you have already noticed, a string doesn't execute. You have to use the call operator & to invoke the command that is named by the string that follows.
It's taking the \ to be part of the variable name, and complains because it is not legal. If you are using this snippet like i would, by putting it into a .ps1 file in your path, then i would just cd over to $putty_path if you don't want to have pscp.exe in your global PATH env var.
Just guessing, but I have a feeling you might be misusing the curly braces. Are you trying to get the environment variable PROXY_USER instead? Typically the curly brackets are used for starting a new statement block.
$Env:PROXY_USER
Also, you may want to encapsulate that proxy info inside a string to ensure it is treated as a single argument:
"$Env:PROXY_USER#$Env:PROXY_HOST:$Env:PROXY_DIR"