Powershell: call an exe with parameters from items in an array? - arrays

I have a set of commands that I'd like to run and do some returncode checks against the result, so i figured it would be easy to put them into an array for execution.
Let's take this one as an example:
C:\windows\system32\inetsrv\AppCmd.exe set config "Default Web Site/" /section system.webServer/webdav/authoring /enabled:true /commit:apphost
Now, when I put it into my array without any quotes it's immediately interpreted as a command, the command is executed and the result is written into the array:
$commands = #()
$commands += C:\windows\system32\inetsrv\AppCmd.exe set config "Default Web Site/" /section system.webServer/webdav/authoring /enabled:true /commit:apphost
When I use quotes to put it into the array as a string, trying to execute it doesn't work.
PS> $commands = #()
PS> $commands += "C:\windows\system32\inetsrv\AppCmd.exe set config ""Default Web Site/"" /sectio n:system.webServer/webdav/authoring /enabled:true /commit:apphost"
PS> $commands[0]
C:\windows\system32\inetsrv\AppCmd.exe set config "Default Web Site/" /section:system.webServer/webdav/authoring /enabled:true /commit:apphost
PS> & $commands[0]
The term 'C:\windows\system32\inetsrv\AppCmd.exe set config "Default Web Site/" /section:system.webServer/webdav/authoring /enabled:true /commit:apphost' 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:2
+ & <<<< $commands[0]
+ CategoryInfo : ObjectNotFound: (C:\windows\syst.../commit:apphost:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
So, how do I correctly put this command into an array, queue or whatever fits best so I can execute it when the time is right?

When you use the call operator & the string following it must name only the command to be executed - not any parameters. With what you have configured you can use Invoke-Expression instead e.g.:
iex $command[0]
One warning about Invoke-Expression, be careful using this command if any input comes from the user as it can be used to inject unwanted commands e.g.:
Read-Host "Enter commit value"
Enter commit value: apphost; remove-item c:\ -r -force -ea 0 -confirm:0 #

Well, i'm a year late to a party, but you can also do this:
new-alias -name Monkey -value "$env:windir\system32\inetsrv\APPCMD.exe"

Even later: I think this answer and this other answer by user Roman Kuzmin closely address this issue, by storing executable name in a variable and all its parameters in an array.

Related

Powershell returning file size on second item of array; first and third are fine

I have what may be an odd issue. I've got a Powershell script that's supposed to watch a directory for files, then move and rename them. It checks the output directory to see if a file with that name already exists with the form "Trip ID X Receipts Batch Y.pdf" (original output from the web form will always be that Y=1) and if it does replace Y with whatever the highest existing number of Y for other files with Trip ID X is. If there isn't one already, it'll just stay that Y=1. It does this successfully except on the second match, where instead of 2 Y will equal a number that varies depending on the file. This seems to be the file size in bytes plus 1. Example results of the script (from copy/pasting the same source file into the watched directory):
Trip ID 7 Receipts Batch 1.pdf
Trip ID 7 Receipts Batch 126973.pdf
Trip ID 7 Receipts Batch 3.pdf
Trip ID 7 Receipts Batch 4.pdf
The relevant portion of my code is here:
$REFile = "Trip ID " + $TripID + " Receipts Batch "
$TripIDCheck = "Trip ID " + $TripID
$TripFileCount = Get-ChildItem $destination |Where-Object {$_.Name -match $TripIDCheck}
$BatchCount = $TripFileCount.GetUpperBound(0) + 1
$destinationRegEx = $destination + $REFile + $BatchCount + ".pdf"
Move-Item -Path $path -Destination $destinationRegEx -Force
For counting the number of items in the array, I've used what you see above as well as $TripFileCount.Length, and $TripFileCount.Count. They all behave the same, seemingly taking the file, examining its size, setting the Y value for the second item to that, but then treating the third, fourth, etc. items as expected. For the life of me, I can't figure out what's going on. Have any of you ever seen something like this?
Edit: Trying to force $TripFileCount as an array with
$TripFileCount = #(Get-ChildItem $destination |Where-Object {$_.Name -match $TripIDCheck})
doesn't work either. It still does this.
As TessellatingHeckler states, your symptom indeed suggests that you're not accounting for the fact that cmdlets such as Get-ChildItem do not always return an array, but may return a single, scalar item (or no items at all).
Therefore, you cannot blindly invoke methods / properties such as .GetUpperBound() or .Length on such a cmdlet's output. There are two workarounds:
Use array subexpression operator #(...) to ensure that the enclosed command's output is treated as an array, even if only a single object is returned or none at all.
In PSv3+, use the .Count property even on a single object or $null to implicitly treat them as if they were an array.
The following streamlined solution uses the .Count property on the output from the Get-ChildItem call, which works as intended in all 3 scenarios: Get-ChildItem matches nothing, 1 file, or multiple files.
$prefix = "Trip ID $TripID Receipts Batch "
$suffix = '.pdf'
$pattern = "$prefix*$suffix"
$count = (Get-ChildItem $destination -Filter $pattern).Count
Move-Item -Path $path -Destination (Join-Path $destination "$prefix$($count+1)$suffix")
Note:
If you're using PowerShell v2, then prepend # to (...). #(...), the array subexpression operator, ensures that the output from the enclosed command is always treated as an array, even if it comprise just 1 object or none at all.
In PowerShell v3 and above, this behavior is conveniently implicit, although there are caveats - see this answer of mine.

How do I run a piped powershell command from command line?

I am trying to run the following powershell command from the windows 7 command line:
powershell ls 'C:/path to file/' | ForEach-Object {$_.LastWriteTime=Get-Date}
I have encountered several errors. When I run the above command, I get the error:
'ForEach-Object' is not recognized as an internal or external command,
operable program or batch file.
I changed the command to:
powershell ls 'C:/My Programs/CPU Analysis/data/test/' | powershell ForEach-Object {$_.LastWriteTime=Get-Date}
Now I am getting the error:
Property 'LastWriteTime' cannot be found on this object; make sure it exists
and is settable.
At line:1 char:17
+ ForEach-Object {$_.LastWriteTime=Get-Date}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyNotFound
How can I modify this command to work from the command line?
Update
Both solutions are basically saying the same thing, but #Trevor Sullivan has a clearer answer.
cmd.exe doesn't understand foreach object. Plus, you're trying to split execution across two separate PowerShell processes, which is not going to work in this scenario.
You'll need to run the whole command in PowerShell.
powershell "ls 'C:/My Programs/CPU Analysis/data/test/' | ForEach-Object {$_.LastWriteTime = Get-Date}"
I'm not sure what are you trying to achieve..but if you are after files and their last modified time then use this:
powershell "ls 'C:\path' | ft name,LastWriteTime"
All you have to do is enclose your command in double quotes ".

read line from txt file to rename server - powershell

I'm trying to read a certain line from a text file using powershell, which can then be used to run with netdom to rename each DC. The line is always line number 3 and starts with Servername= . I need to grab the text after the equals sign to then use with netdom. It was working at first, but netdom would complain about the syntax. Is this because it is a string?. Now servername is returned as the full line:
C:\config.txt:3:ServerName servername
and not just the servername.
$servername = Select-String -Path "C:\config.txt" -Pattern "servername="
$servername = $servername.tostring().split("=")
netdom renamecomputer localhost /NewName:$servername /force
I think you have to append [1] to the second line in order to select the split string that you need i.e:
$servername = $servername.tostring().split("=")[1]

How to convert this sqlcmd script to an Invoke-Sqlcmd script?

I have a SQL script designed to be executed by sqlcmd, and a Command script that executes sqlcmd with the correct parameters.
I want to convert the Command script to a PowerShell script that uses Invoke-Sqlcmd instead of sqlcmd.
The SQL script, the Command script, and the new PowerShell script all live in the directory C:\Users\iain.CORP\SqlcmdQuestion.
SQL Script
The SQL script is called ExampleQuery.sql. It selects a string literal. The value of the string literal is set by sqlcmd at runtime to the value of the ComputerName sqlcmd scripting variable. The code looks like this:
SELECT '$(ComputerName)';
Command Script
The command script is called ExecQuery.cmd. It calls sqlcmd to execute ExampleQuery.sql and sets the value of the scripting variable ComputerName to the value of the environment variable COMPUTERNAME. The code looks like this:
sqlcmd -i ExampleQuery.sql -v ComputerName = %COMPUTERNAME%
When I open a command prompt, the default working directory is C:\Users\iain.CORP. I change the to the directory containing the files, and run the Command script:
cd C:\Users\iain.CORP\SqlcmdQuestion
ExecQuery.cmd
I see this output:
---------
SKYPC0083
(1 rows affected)
The script successfully selects a string literal set by sqlcmd.
PowerShell Script
The PowerShell script is called ExecQuery.ps1. It is supposed to do the same as the command script, using Invoke-Sqlcmd instead of sqlcmd. The code looks like this:
Add-PSSnapin SqlServerCmdletSnapin100
Add-PSSnapin SqlServerProviderSnapin100
Invoke-Sqlcmd -InputFile 'ExampleQuery.sql' -Variable "ComputerName = $Env:COMPUTERNAME"
When I open a PowerShell prompt, the default working directory is Z:\. I change to the directory containing the files, and run the PowerShell script:
cd C:\Users\iain.CORP\SqlcmdQuestion
.\ExecQuery.ps1
I see this output:
Invoke-Sqlcmd : Could not find file 'Z:\ExampleQuery.sql'.
At C:\Users\iain.CORP\SqlcmdQuestion\ExecQuery.ps1:4 char:14
+ Invoke-Sqlcmd <<<< -InputFile 'ExampleQuery.sql' -Variable "ComputerName = $Env:COMPUTERNAME"
+ CategoryInfo : InvalidResult: (:) [Invoke-Sqlcmd], FileNotFoundException
+ FullyQualifiedErrorId : ExecutionFailed,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand
The PowerShell script raises an error because Invoke-Sqlcmd can't find the the input file in the Z:\ directory, which happens to be the default working directory.
The Command script found the script in the current working directory.
How do I make Invoke-Sqlcmd use the current working directory instead of the default working directory?
For this answer, assume that the directory C:\Users\iain.CORP\SqlcmdQuestion exists and that executing dir at that location produces the following output, as implied by the question:
Directory: C:\Users\iain.Corp\SqlcmdQuestion
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 26/09/2012 15:30 27 ExampleQuery.sql
-a--- 26/09/2012 15:30 61 ExecQuery.cmd
-a--- 26/09/2012 15:34 172 ExecQuery.ps1
PowerShell ignores the working directory by design
My question has a false premise:
How do I make Invoke-Sqlcmd use the current working directory instead of the default working directory?
The cmdlet does use the current working directory. The problem is that I didn't change the working directory at all in my PowerShell session.
In PowerShell, cd is an alias for the Set-Location cmdlet. You can prove this using the Get-Alias cmdlet:
Get-Alias cd
Output:
CommandType Name Definition
----------- ---- ----------
Alias cd Set-Location
Alex Angelopoulos explains:
[A]lthough PowerShell's location is analogous to the working directory, the location is not the same thing as the working directory. In fact, PowerShell doesn't touch the working directory.
Set-Location does not set the working directory. It sets the working location, which is a similar but distinct concept in PowerShell.
You can prove this by inspecting the working directory using the .NET property Environment.CurrentDirectory after setting the working location using cd as in the question:
cd C:\Users\iain.CORP\SqlcmdQuestion
Environment::CurrentDirectory
Output:
Z:\
I would guess this design decision was made to be consistent. The working directory would be undefined when, for example, the working location were set to a registry hive.
Invoke-Sqlcmd violates this design principle
Invoke-Sqlcmd violates PowerShell's general design principle to use the working location rather than the working directory. Most cmdlets use the working location to resolve relative paths, but Invoke-Sqlcmd is an exception.
Using the ILSpy disassembler and a little intuition to inspect the containing assembly Microsoft.SqlServer.Management.PSSnapins, I believe I have found the reason for the error.
I believe that the cmdlet's parameter -InputFile is implemented by the method IncludeFileName. ILSpy's disassembly of the method looks like this:
// Microsoft.SqlServer.Management.PowerShell.ExecutionProcessor
public ParserAction IncludeFileName(string fileName, ref IBatchSource pIBatchSource)
{
if (!File.Exists(fileName))
{
ExecutionProcessor.sqlCmdCmdLet.TerminateCmdLet(new FileNotFoundException(PowerShellStrings.CannotFindPath(fileName), fileName), "ExecutionFailureException", ErrorCategory.ParserError);
return ParserAction.Abort;
}
BatchSourceFile batchSourceFile = new BatchSourceFile(fileName);
pIBatchSource = batchSourceFile;
return ParserAction.Continue;
}
Invoke-Sqlcmd uses the .NET method File.Exists to check whether the specified input file exists. The method's documentation remarks that relative paths are resolved using the working directory:
The path parameter is permitted to specify relative or absolute path
information. Relative path information is interpreted as relative to
the current working directory. To obtain the current working
directory, see GetCurrentDirectory.
This suggests that File.Exists would return false in this case, which would cause the error message seen in the question. You can prove this by executing the method directly from the prompt:
cd C:\Users\iain.CORP\SqlcmdQuestion
[IO.File]::Exists('ExecQuery.sql')
Output:
False
The method returns false, so the cmdlet terminates with a 'file not found' error.
You can work around the unusual behavior
There are two workarounds for Invoke-Sqlcmd using the working directory instead of the working location to resolve relative paths:
Always use an absolute path as the value of the -InputFile parameter. CandiedCode's answer shows how to do this.
Set the working directory and use a relative path.
I solved the problem without side-effects by modifying ExecQuery.ps1 like this:
Add-PSSnapin SqlServerCmdletSnapin100
Add-PSSnapin SqlServerProviderSnapin100
$RestoreValue = [Environment]::CurrentDirectory
[Environment]::CurrentDirectory = Get-Location
Invoke-Sqlcmd -InputFile 'ExampleQuery.sql' -Variable "ComputerName = $Env:COMPUTERNAME"
[Environment]::CurrentDirectory = $RestoreValue
I see this output:
Column1
-------
SKYPC0083
Success!
The new script sets the working directory to match the working location before executing Invoke-Sqlcmd. To avoid unintended side-effects of changing the working directory, the scrtipt restores the working directory value before completing.
Setting the current directory is described in this Channel 9 thread. The example there uses the Directory.SetCurrentDirectory method, but I find it simpler to set the property directly.
You could fully qualify the Inputfile location:
Invoke-Sqlcmd -InputFile 'C:\Users\iain.CORP\SqlcmdQuestion\ExampleQuery.sql' -Variable "ComputerName = $Env:COMPUTERNAME"
And use a variable to drive the script location:
$FileLocation = 'C:\Users\iain.CORP\SqlcmdQuestion\'

Scheduled Task for PowerShell Script with String Array Parameter

I've created a PowerShell script that runs perfectly from the Management Shell. I'm trying to get it setup to work in a scheduled task in Windows Server 2008 R2 and am unsure how to pass the parameters for my string array parameter.
Here is the relevant portion of my script:
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[String]
$BaseDirectory,
[String]
$BackupMethod = "Full",
[Int]
$RemoveOlderThanDays = 0,
[String]
$LogDirectory,
[Int]
$LogKeepDays = 7,
[String[]]
$AdditionalDirectories
)
if ($AdditionalDirectories -and $AdditionalDirectories.Count -gt 0) {
Write-Host " Additional Directories to be included:" -ForegroundColor Green
$AdditionalDirectories | ForEach-Object {
Write-Host " $_" -foregroundcolor green
}
}
The parameter giving the trouble is that last one, $AdditionalDirectories.
From the Shell:
If I run the script from the Management Shell like this, it works perfectly:
.\FarmBackup.ps1 \\SomeServer\Backups Full 0 D:\Logs\Backups 0 "D:\Documents\PowerShell Scripts","D:\SomeFolder"
Result:
Additional Directories to be included:
D:/Documents/PowerShell Scripts
D:/SomeFolder
From Scheduled Task:
Action: Start a program
Program/script: PowerShell.exe
Arguments: -File "D:\Documents\PowerShell Scripts\FarmBackup.ps1" \\SomeServer\Backups Full 0 D:\Logs\Backups 0 "D:\Documents\PowerShell Scripts","D:\SomeFolder"
Result: (From Log File)
Additional Directories to be included:
D:\Documents\PowerShell Scripts,D:\SomeFolder
I've tried a couple of different methods for those parameters but I can't seem to get them to be seen as 2 separate strings in the string array. I'm hardcoding them for now, but it seems like there must be a way to make this work since it's totally valid when run from the shell.
Try using the -Command switch instead of the -File switch, and then use the invocation operator '&'. Here is a link to an example of doing this with scheduled tasks:
http://blogs.technet.com/b/heyscriptingguy/archive/2011/01/12/schedule-powershell-scripts-that-require-input-values.aspx
Something like:
-Command "& 'D:\Documents\PowerShell Scripts\FarmBackup.ps1' '\\SomeServer\Backups' 'Full' 0 'D:\Logs\Backups' 0 'D:\Documents\PowerShell Scripts','D:\SomeFolder'"
I tested this solution by creating a script with the contents:
param([string[]] $x)
Write-Host $x.Count
Then called it in the following two ways:
powershell -File ".\TestScript.ps1" "what1,what2"
with result : 1
and
powershell -Command "& .\TestScript.ps1 what1,what2"
with result: 2
Another option, when the options get too complex and you're tired of fiddling with quotes, backticks, etc is to use the underused -EncodedCommand parameter on PowerShell.exe e.g.:
C:\PS> $cmd = "c:\temp\foo.ps1 'D:\Documents\PowerShell Scripts','D:\SomeFolder'"
C:\PS> $cmd
c:\temp\foo.ps1 'D:\Documents\PowerShell Scripts','D:\SomeFolder'
C:\PS> $bytes = [Text.Encoding]::Unicode.GetBytes($cmd)
C:\PS> $encodedCmd = [Convert]::ToBase64String($bytes)
C:\PS> $encodedCmd
YwA6AFwAdABlAG0AcABcAGYAbwBvAC4AcABzADEAIAAnAEQAOgBcAEQAbwBjAHUAbQBlAG4AdABzAFwAUABvAHcAZQByAFMAaABlAGwAbAAgAFMAYwByAGkAcAB0AHMAJwAsACcARAA6AFwAUwBvAG0AZQBGAG8AbABkAGUAcgAnAA==
C:\PS> powershell.exe -encodedCommand YwA6AFwAdABlAG0AcABcAGYAbwBvAC4AcABzADEAIAAnAEQAOgBcAEQAbwBjAHUAbQBlAG4AdABzAFwAUABvAHcAZQByAFMAaABlAGwAbAAgAFMAYwByAGkAcAB0AHMAJwAsACcARAA6AFwAUwBvAG0AZQBGAG8AbABkAGUAcgAnAA==
param1[0] is D:\Documents\PowerShell Scripts
param1[1] is D:\SomeFolder
Admittedly, not something that would be exactly readable/understandable by someone else. :-) You'd have to doc the command in the description of the scheduled task.
I have been using PowerShell for SharePoint and have created several Scheduled Task to trigger them at various intervals.
This is how i do it and it worked for me all the time.
Syntax : [Path to ur script'] -Param1Name 'Value1' -Param2Name 'Value2' -Param3Name 'Value3'
Here is a real example :
D:\Scripts\Global.ps1 -DataLocation 'D:\Scripts' -DeploymentParameters 'Deploymentparameters' -Deployments 'Deployments' -GlobalParameters 'GlobalParameters' -SiteUrl 'https://my.sp.company.com'

Resources