I had all this working but must have changed something because it doesn't now! I want to pass in variables and then from them create a mapped drive.
$d = "O:";
$s = "\\Server\Folder";
$u = "/user:DOMAIN\UserId password";
net use $d $s $u;
When I run that now, powershell just hangs. I don't get any errors or nothing it just "runs" but doesn't complete. I have to hit the stop script button. If I check net use there is no entry for it in there.
if I manually type net use O: \\Server\Folder /user:DOMAIN\UserId password then it works and creates the mapped drive.
I have tried escaping the backslash as per below:
$d = "O:";$s = "\\\\Server\Folder";$u = "/user:DOMAIN\\UserId password";
also building the string like (with and without the escaped backslashes.):
$n = $d+' '+$s+' '+$u;
net use n;
this method advises the network cannot be found, again, with and without the escaped backslashes. How ever If I echo $n and paste the code it creates the drive so I am totally at a loss!
Calling a CMD command within a PowerShell script you should use Invoke-Expression.
$d = "O:";
$s = "\\Server\Folder";
$u = "/user:DOMAIN\UserId password";
Invoke-Expression "C:\Windows\System32\net.exe use $d $s $u"
Related
I'm using powershell to transfer data to my server.
I had an original question here: Powershell downloadFile with special characters about trying to escape a string inside of batch, which I had no luck with. So I created the short powershell script here:
$model = $args[0]
$os = $args[1]
$gpu = $args[2]
$hdd = $args[3]
$ram = $args[4]
$processor = $args[5]
$realmodel = $args[6]
$realupc = $args[7]
$productnumber = $args[8]
$url = [uri]::EscapeUriString("http://example.com/otherdb/modelfinder/form.php?model=$model&os=$os&graphics=$gpu&hdd=$hdd&ram=$ram&processor=$processor&actualmodel=$realmodel&Implement=0&UPC=$realupc&otherIdentifier=$productnumber")
(New-Object Net.WebClient).DownloadFile($url, 'save.txt')
Which successfully escapes the string and sends it to my server to be processed.
The problem is, near the end of my url, there is sometimes a pound sign # - which I believe powershell may be counting as a comment, or simply isn't being encoded. When checking my database after the url is sent, the # and everything after it is removed from the cell.
How can I encode the string to be sent exactly the way it is to be saved in my database?
You should escape your arguments separately as data strings. When you use [uri]::EscapeUriString("#") you will see that # will not be escaped.
PS > [uri]::EscapeUriString("#");
#
PS > [uri]::EscapeDataString("#")
%23
So as an abbreviated example, you could construct your string like this:
$sb = [System.Text.StringBuilder]::new();
$model = "# model with hash sign"
$os = "some operating system with exclamation mark!!";
$sb.Append("http://example.com/otherdb/modelfinder/form.php?model=");
$sb.Append([uri]::EscapeDataString($model).ToString());
$sb.Append("&os=");
$sb.Append([uri]::EscapeDataString($os).ToString());
$sb.ToString();
http://example.com/otherdb/modelfinder/form.php?model=%23%20model%20with%20hash%20sign&os=some%20operating%20system%20with%20exclamation%20mark!!
I'm creating a function that does some things to Excel files. The list of Excel files are passed into the function as an array called $excelFiles. The code shown below is in progress (it does not yet do all the things it's intended to do). This code, as written so far, appears to be failing because there are no quotes around the string held in $excelFile that sets the $wb variable (right before the nested foreach):
Function CovertExcelFileToTextFiles ($excelFiles)
{
# create an Excel application object (fire off Excel in the background)
$excelApp = New-Object -ComObject Excel.Application
$excelApp.Visible = $false
$excelApp.DisplayAlerts = $false
# get first 3 letters of each file's name
foreach ($excelFile in $excelFiles)
{
$name = Split-Path $excelFile.FullName -Leaf #get filename only from full path
$prefix = $name.Substring(0,3) #get first 3 letters of filename
#look at contents of this variable
$excelFile
$wb = $excelApp.Workbooks.Open($excelFile)
foreach ($ws in $wb.Worksheets)
{
$n = $prefix + "_"+ $ws.Name
$n
}
}
$excelApp.Quit()
}
Here is the error that appears in the console:
The reason I suspect the problem is lack of quotes is because the code works if $wb is set to a hardcoded file path.
I'm having difficulty figuring out how to get double quotes around the variable to feed into the line that sets $wb. I have tried "$excelFile" and the editor puts a red squiggly line under it so apparently that's not allowed. I have also tried creating a new variable and populating that with "$excelFile", then plugging that into the parenthesis in the $wb line. That causes an error in the console as well. How can double quotes be put around $excelFile?
Lack of quotes is not your issue. When you hardcode a file and use quotes, the quotes tell the parser that it's a string; they aren't part of the string value.
You need to know what data type $excelFile is (that is, $excelFiles is an array of what?).
If your use of $excelFile.FullName is correct, then it seems you're dealing with an object.
In that case, the .Open() method is likely expecting a [String] and won't understand the object you're passing it, so try:
$wb = $excelApp.Workbooks.Open($excelFile.FullName)
A good way to troubleshoot this kind of thing is to use PowerShell ISE, then set a breakpoint.
In your case, set a breakpoint on the $wb = ... line, execute, and when the breakpoint is hit, execution will stop at that line (before executing it).
At that point, you can use the console to execute statements that will be run in the context of your running code. So for example you could run:
$excelFile.GetType()
or
$excelFile | Get-Member
and learn some things about the object you're dealing with. You could look at its properties, etc.:
$excelFile.FullName
You can look at the overloads of the method you're calling:
$excelApp.Workbooks.Open # <-- note no parentheses
Imagine the following code:
# Script Start
$WelcomeMessage = "Hello $UserName, today is $($Date.DayOfWeek)"
..
..
# 100 lines of other functions and what not...
..
function Greet-User
{
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
$WelcomeMessage
}
This is a very basic example, but what it tries to show is a script where there is a $WelcomeMessage that the person running the script can set at the top of the script and controls how/what the message displayed is.
First thing's first: why do something like this? Well, if you're passing your script around to multiple people, they might want different messages. Maybe they don't like $($Date.DayOfWeek) and want to get the full date. Maybe they don't want to show the username, whatever.
Second, why put it at the top of the script? Simplicity. If you have 1000 lines in your script and messages like these spread all over the script, it makes it a nightmare for people to find and change these messages. We already do that for static messages, in the form of localized strings and stuff, so this is nothing new, except for the variable parts in it.
So, now to the issue. If you run that code and invoke Greet-User (assuming the functions/cmdlets for retrieving username and date actually exist and return something proper...) Greet-User will always return Hello , today is.
This is because the string is expanded when you declare it, at the top of the script, when neither $UserName nor $Date objects have a value.
A potential workaround would be to create the strings with single quotes, and use Invoke-Expression to expand them. But because of the spaces, that gets a bit messy. I.e.:
$WelcomeMessage = 'Hello $env:USERNAME'
Invoke-Expression $WelcomeMessage
This throws an error because of the space, to get it to work properly it would have to be declared as such:
$WelcomeMessage = 'Hello $env:USERNAME'
$InvokeExpression = "`"$WelcomeMessage`""
Messy...
Also, there's another problem in the form of code injection. Since we're allowing the user to write their own welcome message with no bounds specified, what's to prevent them from putting in something like...
$WelcomeMessage 'Hello $([void] (Remove-Item C:\Windows -Force -Recurse))'
(Yes, I know this will not delete everything but it is an example)
Granted this is a script and if they can modify that string they can also modify everything else on the script, but whereas the example I gave was someone maliciously taking advantage of the nature of the script, it can also happen that someone accidentally puts something in the string that ends up having unwanted consequences.
So... there's got to be a better way without the use of Invoke-Expression, I just can't quite thing of one so help would be appreciated :)
Embedding variables into strings is not the only way to create dynamic text, the way I would do it is like this:
$WelcomeMessage = 'Hello {0}, today is {1}'
# 100 lines of other functions and what not...
function Greet-User
{
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
$WelcomeMessage -f $Username, $Date
}
The canonical way to delay evaluation of expressions/variables in strings is to define them as single-quoted strings and use $ExecutionContext.InvokeCommand.ExpandString() later on.
Demonstration:
PS C:\> $s = '$env:COMPUTERNAME'
PS C:\> $s
$env:COMPUTERNAME
PS C:\> $ExecutionContext.InvokeCommand.ExpandString($s)
FOO
Applied to your sample code:
$WelcomeMessage = 'Hello $UserName, today is $($Date.DayOfWeek)'
...
...
...
function Greet-User {
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
$ExecutionContext.InvokeCommand.ExpandString($WelcomeMessage)
}
Have you considered using a lambda expression; i.e. instead of defining the variable as a string value define it as a function, then invoke that function passing the relevant parameters at runtime.
$WelcomeMessage = {param($UserName,$Date);"Hello $UserName, today is $($Date.DayOfWeek) $([void](remove-item c:\test\test.txt))"}
#...
# 100 lines of other functions and what not...
#...
"testfile" >> c:\test\test.txt #ensure we have a test file to be deleted
function Get-UserNameFromSomewhereFancy(){return "myUsername";}
function Get-DateFromSomewhereFancy(){return (get-date);}
function Greet-User
{
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
$WelcomeMessage.invoke($username,$date)
}
cls
Greet-User
Update
If you only wish to allow variable replacement the below code would do the trick; but this fails to do more advanced functions (e.g. .DayOfWeek)
$WelcomeMessage = 'Hello $Username, today is $($Date.DayOfWeek) $([void](remove-item c:\test\test.txt))'
#...
# 100 lines of other functions and what not...
#...
"testfile" >> c:\test\test.txt #ensure we have a test file to be deleted
function Get-UserNameFromSomewhereFancy(){return "myUsername";}
function Get-DateFromSomewhereFancy(){return (get-date);}
function Resolve-WelcomeMessage(){
write-output {param($UserName,$Date);"$WelcomeMessage";}
}
function Greet-User
{
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
$temp = $WelcomeMessage
get-variable | ?{#('$','?','^') -notcontains $_.Name} | sort name -Descending | %{
$temp = $temp -replace ("\`${0}" -f $_.name),$_.value
}
$temp
}
cls
Greet-User
Update
To avoid code injection this makes use of -whatif; that will only help where the injected code supports the whatif functionality, but hopefully better than nothing...
Also the code now doesn't require parameters to be declared; but just takes those variables which are available at the time of execution.
$WelcomeMessage = {"Hello $Username, today is $($Date.DayOfWeek) $([void](remove-item c:\test\test.txt))"}
#...
# 100 lines of other functions and what not...
#...
function Get-UserNameFromSomewhereFancy(){return "myUsername";}
function Get-DateFromSomewhereFancy(){return (get-date);}
function Resolve-WelcomeMessage(){
write-output {param($UserName,$Date);"$WelcomeMessage";}
}
"testfile" >> c:\test\test.txt #ensure we have a test file to be deleted
function Greet-User {
[cmdletbinding(SupportsShouldProcess=$True)]
param()
begin {$original = $WhatIfPreference; $WhatIfPreference = $true;}
process {
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
& $WelcomeMessage
}
end {$WhatIfPreference = $original;}
}
cls
Greet-User
How can I force string variable expansion?
I need to read a string with one or more variable names in it (a template) and then expand it after I read the file. The key is that I must read the contents of the file before I declare the variables that will be used in the expansion. I've tried several ways but I can't get it to work.
It's not an option to read the file after $environment is defined.
Contents of name.txt:
$environment-RPT-INT
#example 1
$name = gc "c:\temp\name.txt"
$environment = "9065DEV"
$expanded = $ExecutionContext.InvokeCommand.ExpandString($name)
$expanded
#example 2
$name = gc "c:\temp\name.txt"
$environment = "9065DEV"
$expanded = $expanded = Invoke-Expression "`"$template`""
$expanded
#example 3
$name = gc "c:\temp\name.txt"
$environment = "9065DEV"
$name = $name.Clone()
$expanded = $ExecutionContext.InvokeCommand.ExpandString($name)
$expanded
Any help is appreciated.
Updated: Example 1 is now working for me.
It looks like you've found some possible solutions, but I'll suggest another that is in my opinion a bit smarter and more robust.
Instead of requiring variable names in your text file, why not use format specifiers. For example, the contents of name.txt:
{0}-RPT-INT
And in your script:
$name = gc "c:\temp\name.txt"
$environment = "9065DEV"
$expanded = $name -f $environment
$expanded
This way, you can rename the variable w/o changing any of your text files. As a bonus, if your text file comes from unknown sources, your script is vulnerable to code injection. For example, say you are given a text file with these contents:
$(rm -whatif -recurse -force c:\)-RPT-INT
Then ExpandString or Invoke-Expression will happily execute that code.
Your Invoke-Expression example is pretty close. Instead of using $template though, you need to use $name.
#example 2
$name = gc 'c:\temp\name.txt';
$environment = '9065DEV';
$expanded = Invoke-Expression -Command "`"$name`"";
$expanded;
If you are willing to store your Setting/Values in a CSV, I wrote a module to pull values from a CSV, put it into a HereString... any variables you put into the CSV become fully expanded inside the Here-String. That way, you can normally address the field names and values.
I might be able to change this to also work with traditional INI's if anyone is interested.
https://github.com/Inventologist?tab=repositories
Look for: HereStringFromCSV
There is a function called ExpandString predefined in powershell. It's inside $ExecutionContext, as follows.
$mystring = #'
This is a here string with some embedded variables.
Here is variable foo -- $foo
Here is variable bar -- $bar
Here is variable bletch -- $bletch
'#
#This displays the here string as is.
$mystring
#now define foo, bar and bletch
$foo = 5
$bar = Get-Date
$bletch = "George Washington"
#now run the here string through Expandstring.
$ExecutionContext.InvokeCommand.ExpandString($mystring)
This is what I want to execute:
c:\Program Files (x86)\SEQUEL ViewPoint\viewpoint.exe /Setvar((POSTSTR $POSTSTR)(POSTEND $POSTEND)) /G:C:\viewpointfile.vpt /D:C:($BEGDATE to $TODDATE).xls
This is what I have tried:
$a = "/Setvar((POSTSTR $POSTSTR)(POSTEND $POSTEND))"
$b = "/G:C:\viewpointfile.vpt"
$c = "/D:C:($BEGDATE to $TODDATE).xls"
$Viewpoint = "c:\Program Files (x86)\SEQUEL ViewPoint\viewpoint.exe"
&$Viewpoint $a $b $c
When I execute this I receive an error stating:
File C:\viewpointfile.vpt "/D:C:($BEGDATE to $TODDATE).xls" not found!
I'm not sure where it gets the extra quotes from. If I run the command with just $a and $b it runs fine.
Any help would be greatly appreciated. Thanks! :)
Update
manojlds suggested echoargs so here it the output from it:
&./echoargs.exe $viewpoint $a $b $c
Arg 0 is C:\Program Files (x86)\SEQUEL ViewPoint\viewpoint.exe
Arg 1 is /Setvar((POSTSTR 20101123)(POSTEND 20111123))
Arg 2 is /G:C:\viewpointfile.vpt
Arg 3 is /D:C:(2010-11-23 to 2011-11-23 PM).xls
It appears that all the arguments are being passed properly. When I run this as a command in cmd.exe it executes perfectly. So something on Powershells end must be messing up the output.
Is there any other way to go about executing this command using Powershell?
I've found the method blogged by Joel Bennett to be the most reliable when calling legacy commands
http://huddledmasses.org/the-problem-with-calling-legacy-or-native-apps-from-powershell/
I've had to use this when calling LogParser from Powershell:
set-alias logparser "C:\Program Files (x86)\Log Parser 2.2\LogParser.exe"
start-process -NoNewWindow -FilePath logparser -ArgumentList #"
"SELECT * INTO diskspaceLP FROM C:\Users\Public\diskspace.csv" -i:CSV -o:SQL -server:"Win7boot\sql1" -database:hsg -driver:"SQL Server" -createTable:ON
"#
Get echoargs.exe from Powershell community extension ( http://pscx.codeplex.com/ ) to figure out the arguments that Powershell sends to your exe.
$a = "/Setvar((POSTSTR $POSTSTR)(POSTEND $POSTEND))"
$b = "/G:C:\viewpointfile.vpt"
$c = "/D:C:($BEGDATE to $TODDATE).xls"
$echoArgs = ".\echoargs.exe"
&$echoArgs $a $b $c
You seem to be passing the arguments fine however, but the viewpoint.exe seems to be acting up. I don't see what you are doing here:
$c = "/D:C:($BEGDATE to $TODDATE).xls"
After C: there is no \ and also your error message that you have pasted shows $BEGDATE and $TODDATE verbatim, which is not possible as they would have been substituted with their values.
If I can't run a command like this it usually works for me with Invoke-Expression. Can't test yours though.
Invoke-Expression "$viewpoint $a $b $c"