Problem Executing NirSoft SearchMyFiles.exe from Powershell - powershell

I know this is a problem in how I'm passing the arguments but I can't figure it out as it looks like I've successfully escaped the characters that might cause a problem.
PS> $ExecutionPath = "G:\BEKDocs\NonInstPrograms\NirSoftx64"
$PgmName = "Searchmyfiles.exe"
$RunCmd = Join-Path -Path "$ExecutionPath" -ChildPath "$PgmName"
$MyArgs = " `/config `"G:\BEKDocs\TestSMF.cfg`" `/StartSearch `/ExplorerCopy `/stext `"G:\BEKDocs\TestSMF.txt`""
$runcmd += $MyArgs
& $Runcmd
---Results: No file Created no Error messages displayed---
--- Show the Created Command ---
PS> $RunCmd
G:\BEKDocs\NonInstPrograms\NirSoftx64\Searchmyfiles.exe /config "G:\BEKDocs\TestSMF.cfg" /StartSearch /ExplorerCopy /stex
t "G:\BEKDocs\TestSMF.txt"
--- Try to Execute the command from History ---
PS> G:\BEKDocs\NonInstPrograms\NirSoftx64\Searchmyfiles.exe /config "G:\BEKDocs\TestSMF.cfg" /StartSearch /ExplorerCopy /stex
t "G:\BEKDocs\TestSMF.txt"
--- Result: No file created no Error messages displayed. ---
--- Type the command by hand ---
PS> G:\BEKDocs\NonInstPrograms\NirSoftx64\searchmyfiles.exe /config "G:\BEKDocs\TestSMF.cfg" /StartSearch /ExplorerCopy /stext "G:\BEKDocs\TestSMF.txt"
--- Result: File CREATED as expected ---
PS>
I'm at a loss!

In testing it appears that it is treating $Runcmd — both executable path and parameters — as one complete executable path to execute. I believe this is explained by the following section of about_Operators...
The call operator does not parse strings. This means that you cannot use command parameters within a string when you use the call operator.
PS> $c = "Get-Service -Name Spooler"
PS> $c
Get-Service -Name Spooler
PS> & $c
& : The term 'Get-Service -Name Spooler' 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.
Instead, pass the executable path and parameters to the call operator separately...
$ExecutionPath = "G:\BEKDocs\NonInstPrograms\NirSoftx64"
$PgmName = "Searchmyfiles.exe"
$RunPath = Join-Path -Path "$ExecutionPath" -ChildPath "$PgmName"
$MyArgs = #(
'/config', '"G:\BEKDocs\TestSMF.cfg"',
'/StartSearch',
'/ExplorerCopy',
'/stext', '"G:\BEKDocs\TestSMF.txt"'
)
& $RunPath $MyArgs
You'll notice that $MyArgs is now an array and not a [String]. It seems that if $MyArgs is a [String] or a single-element array it gets passed to the executable as one parameter surrounded by double quotes. Stored as above each array element gets passed as a parameter with no additional quoting. If you still want to define $MyArgs using one line of text you could do so like this...
$MyArgsText = '/config "G:\BEKDocs\TestSMF.cfg" /StartSearch /ExplorerCopy /stext "G:\BEKDocs\TestSMF.txt"'
# NOTE: This only works because the path parameters contain no spaces
$MyArgs = $MyArgsText -split ' '
All of the above is why, for anything but ad hoc commands, I would prefer the self-documenting, obvious-as-to-which-string-is-treated-as-which nature of a Start-Process call with explicitly-named parameters over the call operator. Given...
$ExecutionPath = "G:\BEKDocs\NonInstPrograms\NirSoftx64"
$PgmName = "Searchmyfiles.exe"
$RunPath = Join-Path -Path "$ExecutionPath" -ChildPath "$PgmName"
$MyArgsText = '/config "G:\BEKDocs\TestSMF.cfg" /StartSearch /ExplorerCopy /stext "G:\BEKDocs\TestSMF.txt"'
...then both this...
Start-Process -FilePath $RunPath -ArgumentList $MyArgsText
...and this...
# NOTE: This only works because the path parameters contain no spaces
$MyArgs = $MyArgsText -split ' '
Start-Process -FilePath $RunPath -ArgumentList $MyArgs
...execute $RunPath with the same parameters.

I think every time I tried to include both the EXE name and the parameters in the same string, The call operator (&) fails. With that in mind, try something where $Runcmd is pointing only to the EXE and nothing else. This also makes testing easy by temporarily placing the path to EchoArgs in $Runcmd to view the actual parameters the EXE will be receiving.
This code example uses the Stop-parsing token (--%) and environmental variables to build the parameters passed to the EXE in $Runcmd.
$ExecutionPath = "G:\BEKDocs\NonInstPrograms\NirSoftx64"
$PgmName = "Searchmyfiles.exe"
$RunCmd = Join-Path -Path "$ExecutionPath" -ChildPath "$PgmName"
$Env:Config = '"G:\BEKDocs\TestSMF.cfg"'
$Env:SText = '"G:\BEKDocs\TestSMF.txt"'
& $Runcmd --% /config %Config% /StartSearch /ExplorerCopy /stext %SText%

Related

Run PowerShell script from another script, passing string variables

I got two Powershell script.
The first, script1.ps1, got some Global string variable.
The second, script2.ps1, must do some operation with values of the string variables, then close without passing any data to the first.
Here pieces of code.
script1.ps1
#[ENG] Global $language contains the language name: EN-US for american english, EN-GB for english of Great Britain, for example.
$global:language = $culture.Name
#[ENG] Global $workPath variable contains the path of the folder with the original files.
$global:workPath = "D:\Documents\Downloads\Emule-Incoming\osis"
#[ENG] Global $scriptPath variable contains the path of the script(s) files.
$local:temp = Split-Path $PSCommandPath
$global:scriptPath = "$temp"
$temp = ""
##########
# mods.d #
##########
#[ENG] Global $modBip variable contains the path of destination of CONF files for BPBiblePortable.
$global:modBip = "D:\Documents\Downloads\Emule-Incoming\BPBiblePortable\App\BPBible\resources\mods.d\"
#[ENG] Global $modXip contains the path of destination of CONF files for xiphos.
$global:modXip = "C:\Users\Emanuele\AppData\Roaming\Sword\mods.d\"
#[ENG] Global $modGit variable contains the path of destination of CONF files in GitHub Desktop to upload and synchronize in GitHub.
$global:modGit = "D:\Documents\GitHub\EmanueleTinari\mods.d\"
$scriptPath = $scriptPath + "\" + 'cei1974.ps1 $language $workPath $scriptPath $modBiP $modXip $modGit'
Invoke-Expression & $scriptPath
script2.ps1
#[ENG] Retrieve Global variable's values.
Param ([string]$language, [string]$workPath, [string]$scriptPath, [string]$modBiP, [string]$modXip, [string]$modGit)
Write-Host $language
Write-Host $workPath
Write-Host $scriptPath
Write-Host $modBiP
Write-Host $modXip
Write-Host $modGit
What I try:
$scriptPath = $scriptPath + "\" + 'cei1974.ps1 -$language $language -$workPath $workPath -$scriptPath $scriptPath -$modBiP $modBiP -$modXip $modXip -$modGit $modGit'
Invoke-Expression & $scriptPath
I add -$[string name] : nothing to do.
$scriptPath = $scriptPath + "\" + 'cei1974.ps1 -$language $language -$workPath $workPath -$scriptPath $scriptPath -$modBiP $modBiP -$modXip $modXip -$modGit $modGit'
Invoke-Expression $scriptPath
Without & near $scriptPath : nothing to do.
I, first write this question, examined and tryed answers in this posts:
Run a PowerShell script from another one
PowerShell Script call out from another PowerShell Script
Invoke powershell script from another
Passing varibale into powershell script that is being executed from another script
How can I achieve to pass strings between this two script? Tnx in advance.
Swap-out the last 2 lines in your script1.ps1 script with this:
. $scriptPath\script2.ps1 -language $language -workPath $workPath -scriptPath $scriptPath -modBiP $modBiP -modXip $modXip -modGit $modGit
Some notes:
Do not use Invoke-Expression followed by the ampersand (&) call operator, as that is unnecessary.
You have all the information necessary to call script2.ps1 directly, rather than build-up a string and rely on expression evaluation.
When specifying script command-line parameters by name, simply use a hyphen (-) followed by the parameter name. For example, -language $language.
I believe you want to use dot-sourcing (.) to call your script2.ps1. You could also defer to using the ampersand (&) call operator. Invoke-Command and Invoke-Expression could be used, but can also be exploited if not handled correctly.
For more information, see Get-Help about_Operators and Get-Help about_Scripts and Get-Help about_Parameters

powershell : pipe get-content to ps1 file with parameters

I'm trying to write a script which uses the powershell cmdlet get-content tail and inserts the new lines into the sql server table. i can't get the syntax to pipe the tail to the sqlinsert.ps1 file that handles the table insert.
i'm looking for help on how to pipe "get-content tail" to a sqlinsert.ps1 file to do a sql database insert statement using the following :
$startTime = get-date
Write-Host "\\iisserver\logs\Logs-$("{0:yyyyMMdd}" -f (get-date)).txt"
get-content "\\iisserver\logs\Logs-$("{0:yyyyMMdd}" -f (get-date)).txt" -tail 1 -wait | & "sqlinsert.ps1" -stmp $("{0:yyyy-MM-dd hh:mm:ss.fff}" -f (get-date)) -method "Error" -msg $_
# % { "$_ read at $(Get-Date -Format "hh:mm:ss")" }
in the sqlinsert.ps1 :
param ([string]$stmp, [string]$method, [string]$msg )
$Connection = New-Object System.Data.SQLClient.SQLConnection
$Connection.ConnectionString = "server='$serverName';database='$databaseName';User ID = $uid; Password = $pwd;"
$Command = New-Object System.Data.SQLClient.SQLCommand
$Command.Connection = $Connection
$sql = "insert into [tbl_iiserrors] (errstamp, method, msg) values (#stmp , #method, #msg) "
.
.
.
error i get:
& : The term 'sqlinsert.ps1' 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 C:\Temp\ob\iislog\tst_tail.ps1:3 char:95
... Mdd}" -f (get-date)).txt" -tail 1 -wait | & "sqlinsert.ps1" -stmp $ ...
~~~~~~~~~~~~~~~
CategoryInfo : ObjectNotFound: (sqlinsert.ps1:String) [], CommandNotFoundException
FullyQualifiedErrorId : CommandNotFoundException
Suggestion [3,General]: The command sqlinsert.ps1 was not found, but
does exist in the current location. Windows PowerShell does not load
commands from the current location by default. If you trust this
command, instead type: ".\sqlinsert.ps1". See "get-help
about_Command_Precedence" for more details.
The sqlinsert.ps1 works when i run it from powershell command :
PS c:\temp> .\sqlinsert -stmp 2020-11-20 00:00:00 -method 'eek' -msg 'uh hello'
In order to bind pipeline input to a parameter, you need to decorate it with a [Parameter] attribute and specify that it accepts pipeline input, like this:
param (
[string]$stmp,
[string]$method,
[Parameter(ValueFromPipeline = $true)]
[string]$msg
)
See the about_Functions_Advanced_Parameters help file for more details about how to modify the behavior of parameters
By design, for security reasons, PowerShell requires you to signal the intent to execute a script located in the current directory explicitly, using a path - .\sqlinsert.ps1 - rather than a mere file name - sqlinsert.ps1; that is what the suggestion following the error message is trying to tell you.
Note that you only need &, the call operator, if the script path is quoted and/or contains variable references - and .\sqlinsert.ps1 doesn't require quoting.
You can only use the automatic $_ variable, which represents the current input object from the pipeline inside a script block ({ ... }), such as one passed to the ForEach-Object cmdlet, which invokes that block for each object received via the pipeline.
Re the content of your script: Inside expandable strings ("..."), you cannot use # to refer to variables to be expanded (interpolated); use regular, $-prefixed variable references or $(...), the subexpression operator to embed expressions; also, it looks like you're inserting string values into the SQL table, so you'll have to enclose the expanded variable values in embedded '...'
$startTime = get-date
Get-Content "\\iisserver\logs\Logs-$("{0:yyyyMMdd}" -f (get-date)).txt" -Tail 1 -Wait |
ForEach-Object {
.\sqlinsert.ps1 -stmp ("{0:yyyy-MM-dd hh:mm:ss.fff}" -f (get-date)) -method "Error" -msg $_
}
The alternative to using a ForEach-Object call is to modify your script to directly receive its -msg argument from the pipeline, as shown in Mathias' answer, in which case you must omit the -msg $_ argument from your script call:
Get-Content ... |
.\sqlinsert.ps1 -stmp ("{0:yyyy-MM-dd hh:mm:ss.fff}" -f (get-date)) -method "Error"

How to get back result as returned object type when executing script as PowerShell.exe -File

If I have a script (script.ps1) like below:
$dir = Get-Item "C:\Windows"
$dir
and if I call it like:
$myDir = "script.ps1"
I get back the $dir object in $myDir and if I use:
$myDir.Name
I get:
Windows
I have a scenario where I need to call scripts using
$myDir = Powershell.exe -File "script.ps1"
With this, I end up getting the output in the form of a string array object and not in the form of the script-returned object as the command is apparently executed in a different session. Due to this, I can't call any property. E.g: $myDir.Name returns nothing
My question is, how do I get the returned object type back as the result in the above case. i.e, even when I execute
$myDir = Powershell.exe -File "script.ps1"
$myDir.Name should return Windows. Is it even possible?
Thanks for any help.
You can use the -OutputFormat parameter of powershell.exe.
#Write the command to file
"get-item c:\windows\" | out-file C:\temp\test.ps1
$justastring = powershell.exe -file c:\temp\test.ps1
#this is a string array
$justastring | get-member
$notastring = powershell.exe -outputformat xml -file c:\temp\test.ps1
#this is a Deserialized.System.IO.DirectoryInfo
$notastring | get-member
This output is also able to be saved as a file and brought back in later. See Import-CliXml and Export-CliXml. Note that because they are serialized objects they are no longer "live". Some of the methods you are used to seeing will not be there, property values will no longer update either. Its essentially a snapshot of the command output at the instant that the command ran.
You could try like this :
script.ps1 (returns value of $dir):
$dir = Get-Item "C:\Windows"
return $dir
script2.ps1 (returns name of $dir) :
$myDir = .\script.ps1
$myDir.Name
$myDir.Name returns : Windows

Executing Powershell script from command line with quoted parameters

I am automating the build of a legacy MS Access application, and in one of the steps, I am trying to make an Access executable (.ADE). I have come up with the following code, which is stored in a file (PSLibrary.ps1):
Add-Type -AssemblyName Microsoft.Office.Interop.Access
function Access-Compile {
param (
[Parameter(Mandatory=$TRUE,Position=1)][string]$source,
[Parameter(Mandatory=$TRUE,Position=2)][string]$destination
)
Write-Output "Starting MS Access"
$access = New-Object -ComObject Access.Application
$access.Visible = $FALSE
$access.AutomationSecurity = 1
if (!(Test-Path $source)) { Throw "Source '$source' not found" }
if ((Test-Path $destination)) {
Write-Output "File '$destination' already exists - deleting..."
Remove-Item $destination
}
Write-Output "Compiling '$source' to '$destination'"
$result = $access.SysCmd(603, $source, $destination)
$result
Write-Output "Exiting MS Access"
$access.quit()
}
If I go into the PowerShell ISE and run the command below, then everything works fine, and the expected output is created:
PS C:>& "C:\Temp\PSLibrary.ps1"
PS C:>Access-Compile "C:\Working\Project.adp" "C:\Working\Project.ade"
However, I can't seem to generate the right hocus-pocus to get this running from the command line, as I would in an automated build. For instance,
powershell.exe -command "& \"C:\\Temp\\PSLibrary.ps1\" Access-Compile \"C:\\Temp\\Project.adp\" \"C:\\Temp\\Project.ade\""
What am I doing wrong?
For complex parameters, you can use Powershell's -EncodedCommand parameter. It will accept a Base64 encoded string. No escaping is needed for quotes, slashes and such.
Consider a test function that will print its parameters. Like so,
function Test-Function {
param (
[Parameter(Mandatory=$TRUE,Position=1)][string]$source,
[Parameter(Mandatory=$TRUE,Position=2)][string]$destination
)
write-host "src: $source"
write-host "dst: $destination"
}
Create command to load the script and some parameters. Like so,
# Load the script and call function with some parameters
. C:\Temp\Calling-Test.ps1; Test-Function "some\special:characters?" "`"c:\my path\with\spaces within.ext`""
After the command syntax is OK, encode it into Base64 form. Like so,
[System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes('. C:\Temp\Calling-Test.ps1; Test-Function "some\special:characters?" "`"c:\my path\with\spaces within.ext`""'))
You'll get a Base64 string. Like so,
LgAgAEMAOgBcAFQAZQBtAHAAXABDAGEAbABsAGkAbgBnAC0AVABlAHMAdAAuAHAAcwAxADsAIAAgAFQAZQBzAHQALQBGAHUAbgBjAHQAaQBvAG4AIAAiAHMAbwBtAGUAXABzAHAAZQBjAGkAYQBsADoAYwBoAGEAcgBhAGMAdABlAHIAcwA/ACIAIAAiAGAAIgBjADoAXABtAHkAIABwAGEAdABoAFwAdwBpAHQAaABcAHMAcABhAGMAZQBzACAAdwBpAHQAaABpAG4ALgBlAHgAdABgACIAIgA=
Finally, start Powershell and pass the encoded string as a parameter. Like so,
# The parameter string here is abreviated for readability purposes.
# Don't do this in production
C:\>powershell -encodedcommand LgAgA...
Output
src: some\special:characters?
dst: "c:\my path\with\spaces within.ext"
Should you later want to reverse the Base64 encoding, pass it into decoding method. Like so,
$str = " LgAgA..."
[Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($str))
# Output
. C:\Temp\Calling-Test.ps1; Test-Function "some\special:characters?" "`"c:\my path\with\spaces within.ext`""
PowerShell like Bash can take single or double quotes
PS C:\Users\Steven> echo "hello"
hello
PS C:\Users\Steven> echo 'hello'
hello
this can alleviate some of the headache, also I think you can use the literal backslashes without escaping.
To run PowerShell, choose
Start Menu Programs Accessories
Windows Powershell Windows Powershell

Expanding variables in file contents

I have a file template.txt which contains the following:
Hello ${something}
I would like to create a PowerShell script that reads the file and expands the variables in the template, i.e.
$something = "World"
$template = Get-Content template.txt
# replace $something in template file with current value
# of variable in script -> get Hello World
How could I do this?
Another option is to use ExpandString() e.g.:
$expanded = $ExecutionContext.InvokeCommand.ExpandString($template)
Invoke-Expression will also work. However be careful. Both of these options are capable of executing arbitrary code e.g.:
# Contents of file template.txt
"EvilString";$(remove-item -whatif c:\ -r -force -confirm:$false -ea 0)
$template = gc template.txt
iex $template # could result in a bad day
If you want to have a "safe" string eval without the potential to accidentally run code then you can combine PowerShell jobs and restricted runspaces to do just that e.g.:
PS> $InitSB = {$ExecutionContext.SessionState.Applications.Clear(); $ExecutionContext.SessionState.Scripts.Clear(); Get-Command | %{$_.Visibility = 'Private'}}
PS> $SafeStringEvalSB = {param($str) $str}
PS> $job = Start-Job -Init $InitSB -ScriptBlock $SafeStringEvalSB -ArgumentList '$foo (Notepad.exe) bar'
PS> Wait-Job $job > $null
PS> Receive-Job $job
$foo (Notepad.exe) bar
Now if you attempt to use an expression in the string that uses a cmdlet, this will not execute the command:
PS> $job = Start-Job -Init $InitSB -ScriptBlock $SafeStringEvalSB -ArgumentList '$foo $(Start-Process Notepad.exe) bar'
PS> Wait-Job $job > $null
PS> Receive-Job $job
$foo $(Start-Process Notepad.exe) bar
If you would like to see a failure if a command is attempted, then use $ExecutionContext.InvokeCommand.ExpandString to expand the $str parameter.
I've found this solution:
$something = "World"
$template = Get-Content template.txt
$expanded = Invoke-Expression "`"$template`""
$expanded
Since I really don't like the idea of One More Thing To Remember - in this case, remembering that PS will evaluate variables and run any commands included in the template - I found another way to do this.
Instead of variables in template file, make up your own tokens - if you're not processing HTML, you can use e.g. <variable>, like so:
Hello <something>
Basically use any token that will be unique.
Then in your PS script, use:
$something = "World"
$template = Get-Content template.txt -Raw
# replace <something> in template file with current value
# of variable in script -> get Hello World
$template=$template.Replace("<something>",$something)
It's more cumbersome than straight-up InvokeCommand, but it's clearer than setting up limited execution environment just to avoid a security risk when processing simple template. YMMV depending on requirements :-)