3

I have this PowerShell code that I got from the answer to this question; it show the location/dimensions of the cmd.exe window where the PS code run:

$WindowFunction,$RectangleStruct = Add-Type -MemberDefinition @'
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}
'@ -Name "type$([guid]::NewGuid() -replace '-')" -PassThru

$MyWindowHandle = (Get-Process -Id (Get-WmiObject Win32_Process -Filter "ProcessId=$PID").ParentProcessId).MainWindowHandle

$WindowRect = New-Object -TypeName $RectangleStruct.FullName
$null = $WindowFunction::GetWindowRect($MyWindowHandle,[ref]$WindowRect)

Write-Host $WindowRect.Left $WindowRect.Top $WindowRect.Right $WindowRect.Bottom

When I run this code in a .ps1 script from the command line, it works correctly:

C:\Users\Antonio\Documents\test> powershell Set-ExecutionPolicy -ExecutionPolicy
Unrestricted -Scope Process; .\test.ps1
26 -7 943 738

I want to insert this code in a .BATch file in order to don't have a separate .ps1 file, so I must write the same code in a long line as parameters to powershell command. However, in order to keep the readability, I want to use separate lines in the .bat file and terminate each one with the Batch continuation character ^; this was my first attempt:

@echo off

PowerShell  ^
   $WindowFunction,$RectangleStruct = Add-Type -MemberDefinition '^
   [DllImport("user32.dll", SetLastError = true)]  ^
   [return: MarshalAs(UnmanagedType.Bool)]  ^
   public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);  ^
   [StructLayout(LayoutKind.Sequential)]  ^
   public struct RECT  ^
   {  ^
      public int Left;  ^
      public int Top;  ^
      public int Right;  ^
      public int Bottom;  ^
   }  ^
   ' -Name "type$([guid]::NewGuid() -replace '-')" -PassThru;  ^
   $MyWindowHandle = (Get-Process -Id (Get-WmiObject Win32_Process -Filter "ProcessId=$PID").ParentProcessId).MainWindowHandle;  ^
   $WindowRect = New-Object -TypeName $RectangleStruct.FullName;  ^
   $null = $WindowFunction::GetWindowRect($MyWindowHandle,[ref]$WindowRect);  ^
   Write-Host $WindowRect.Left $WindowRect.Top $WindowRect.Right $WindowRect.Bottom
%End PowerShell%

When I run this Batch file, several errors are reported:

C:\Users\Antonio\Documents\test> test.bat
Add-Type : c:\Users\Antonio\AppData\Local\Temp\yhd4ckqv.0.cs(8) : El nombre
'user32' no existe en el contexto actual
c:\Users\Antonio\AppData\Local\Temp\yhd4ckqv.0.cs(7) :     {
c:\Users\Antonio\AppData\Local\Temp\yhd4ckqv.0.cs(8) : >>>
[DllImport(user32.dll, SetLastError = true)] [return:
MarshalAs(UnmanagedType.Bool)] public static extern bool GetWindowRect(IntPtr
hWnd, ref RECT lpRect); [StructLayout(LayoutKind.Sequential)] public struct
RECT { public int Left; public int Top; public int Right; public int Bottom; }
c:\Users\Antonio\AppData\Local\Temp\yhd4ckqv.0.cs(9) :
En línea: 1 Carácter: 36
+ $WindowFunction,$RectangleStruct = Add-Type -MemberDefinition '
[DllImport(user3 ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~
    + CategoryInfo          : InvalidData: (c:\Users\Antoni...contexto actual:
   CompilerError) [Add-Type], Exception
    + FullyQualifiedErrorId : SOURCE_CODE_ERROR,Microsoft.PowerShell.Commands.
   AddTypeCommand

Add-Type : No se puede agregar el tipo. Hubo errores de compilación.
En línea: 1 Carácter: 36
+ $WindowFunction,$RectangleStruct = Add-Type -MemberDefinition '
[DllImport(user3 ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~

and a long et cetera....

I tried to pass the apostrophe to the line below, changed apostrophes by quotes and viceversa, eliminated additional spaces at beginning of each line and several other modifications, but I couldn't found the right way to write this code. I wrote several PS code segments before much larger than this one in the same way with no problems at all. Although I am an experienced programmer, I am novice to PowerShell and its multiple idiosyncrasies had always confused me...

What is the right way to write this PS code in a Batch file? I'll appreciate it if a simple explanation of the cause of the problem is also included...

Community
  • 1
  • 1
Aacini
  • 65,180
  • 12
  • 72
  • 108
  • Possible duplicate of [Powershell - escaping string passed to child process](http://stackoverflow.com/questions/34276662/powershell-escaping-string-passed-to-child-process) – user4003407 Apr 17 '16 at 05:03
  • @PetSerAl: I am afraid, not... The answer at such link does _not_ solve this problem. As a matter of fact, it have _no relation_ with this problem at all, because in this case a _fully working PS code_ already exist! – Aacini Apr 17 '16 at 06:35
  • 1
    I just try it myself and it works. If you carefully read your error messages, then you can note absence of some double quote characters: `[DllImport(user3`. And linked question is all about that. – user4003407 Apr 17 '16 at 06:43
  • @PetSerAl: You are right! I apologize for my previous comment... – Aacini Apr 18 '16 at 14:58

3 Answers3

5

Double quote literals must be escaped as \"

@echo off

PowerShell^
  $WindowFunction,$RectangleStruct = Add-Type -MemberDefinition '^
  [DllImport(\"user32.dll\", SetLastError = true)]^
  [return: MarshalAs(UnmanagedType.Bool)]^
  public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);^
  [StructLayout(LayoutKind.Sequential)]^
  public struct RECT^
  {^
     public int Left;^
     public int Top;^
     public int Right;^
     public int Bottom;^
  }^
  ' -Name \"type$([guid]::NewGuid() -replace '-')\" -PassThru;^
  $MyWindowHandle = (Get-Process -Id (^
    Get-WmiObject Win32_Process -Filter \"ProcessId=$PID\"^
  ).ParentProcessId).MainWindowHandle;^
  $WindowRect = New-Object -TypeName $RectangleStruct.FullName;^
  $null = $WindowFunction::GetWindowRect($MyWindowHandle,[ref]$WindowRect);^
  Write-Host $WindowRect.Left $WindowRect.Top $WindowRect.Right $WindowRect.Bottom
dbenham
  • 127,446
  • 28
  • 251
  • 390
user4003407
  • 21,204
  • 4
  • 50
  • 60
1

There was a topic on DosTips a while ago that addressed this. You can make a batch/Powershell hybrid with a simple header:

<# :
:: Header to create Batch/PowerShell hybrid
@echo off
setlocal
set "POWERSHELL_BAT_ARGS=%*"
if defined POWERSHELL_BAT_ARGS set "POWERSHELL_BAT_ARGS=%POWERSHELL_BAT_ARGS:"=\"%"
endlocal & powershell -NoLogo -NoProfile -Command "$_ = $input; Invoke-Expression $( '$input = $_; $_ = \"\"; $args = @( &{ $args } %POWERSHELL_BAT_ARGS% );' + [String]::Join( [char]10, $( Get-Content \"%~f0\" ) ) )"

:: Any batch code that gets run after your PowerShell goes here
goto :EOF
#>

Just throw your Powershell code after the #> and save the file as a regular .bat script. In your case:

<# :
:: Header to create Batch/PowerShell hybrid
@echo off
setlocal
set "POWERSHELL_BAT_ARGS=%*"
if defined POWERSHELL_BAT_ARGS set "POWERSHELL_BAT_ARGS=%POWERSHELL_BAT_ARGS:"=\"%"
endlocal & powershell -NoLogo -NoProfile -Command "$_ = $input; Invoke-Expression $( '$input = $_; $_ = \"\"; $args = @( &{ $args } %POWERSHELL_BAT_ARGS% );' + [String]::Join( [char]10, $( Get-Content \"%~f0\" ) ) )"
goto :EOF
#>

$WindowFunction,$RectangleStruct = Add-Type -MemberDefinition @'
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}
'@ -Name "type$([guid]::NewGuid() -replace '-')" -PassThru

$MyWindowHandle = (Get-Process -Id (Get-WmiObject Win32_Process -Filter "ProcessId=$PID").ParentProcessId).MainWindowHandle

$WindowRect = New-Object -TypeName $RectangleStruct.FullName
$null = $WindowFunction::GetWindowRect($MyWindowHandle,[ref]$WindowRect)

Write-Host $WindowRect.Left $WindowRect.Top $WindowRect.Right $WindowRect.Bottom
SomethingDark
  • 13,229
  • 5
  • 50
  • 55
  • Mmmm... Mean this that there is _no way_ to insert this PS code as parameters of `powershell` command? Sorry, but this method does not solve my problem: my large .BATch file application have _two PowerShell sections_ and I want to include this code as part of `:Input` one in order to correctly convert pixel mouse coordinates into row,line text ones. See: http://www.dostips.com/forum/viewtopic.php?f=3&t=6936&p=46206#p46206 – Aacini Apr 17 '16 at 05:30
  • @Aacini - Honestly, I've never tried any other way to merge batch and PowerShell. Sorry I couldn't be of more help. :( – SomethingDark Apr 17 '16 at 05:31
0

I would go for the -EncodedCommand option here. You simply Base64 encode the entire powershell script and then pass the Base64 string as an argument to powershell.exe:

(assuming the script is here: C:\Path\To\Script.ps1)

PS C:\> $ScriptText  = Get-Content C:\Path\To\Script.ps1 -Raw
PS C:\> $ScriptBytes = [System.Text.Encoding]::Unicode.GetBytes($ScriptText)
PS C:\> $EncCommand  = [System.Convert]::ToBase64String($ScriptBytes)

$EncCommand now contains the Base64 encoded command, ready for use inside cmd.exe (or your batch file for that matter):

C:\>powershell -EncodedCommand JABXAGkAbgBkAG8AdwBGAHUAbgBjAHQAaQBvAG4ALAAkAFIAZQBjAHQAYQBuAGcAbABlAFMAdAByAHUAYwB0ACAAPQAgAEEAZABkAC0AVAB5A
HAAZQAgAC0ATQBlAG0AYgBlAHIARABlAGYAaQBuAGkAdABpAG8AbgAgAEAAJwANAAoAWwBEAGwAbABJAG0AcABvAHIAdAAoACIAdQBzAGUAcgAzADIALgBkAGwAbAAiACwAIABTAGUAd
ABMAGEAcwB0AEUAcgByAG8AcgAgAD0AIAB0AHIAdQBlACkAXQANAAoAWwByAGUAdAB1AHIAbgA6ACAATQBhAHIAcwBoAGEAbABBAHMAKABVAG4AbQBhAG4AYQBnAGUAZABUAHkAcABlA
C4AQgBvAG8AbAApAF0ADQAKAHAAdQBiAGwAaQBjACAAcwB0AGEAdABpAGMAIABlAHgAdABlAHIAbgAgAGIAbwBvAGwAIABHAGUAdABXAGkAbgBkAG8AdwBSAGUAYwB0ACgASQBuAHQAU
AB0AHIAIABoAFcAbgBkACwAIAByAGUAZgAgAFIARQBDAFQAIABsAHAAUgBlAGMAdAApADsADQAKAFsAUwB0AHIAdQBjAHQATABhAHkAbwB1AHQAKABMAGEAeQBvAHUAdABLAGkAbgBkA
C4AUwBlAHEAdQBlAG4AdABpAGEAbAApAF0ADQAKAHAAdQBiAGwAaQBjACAAcwB0AHIAdQBjAHQAIABSAEUAQwBUAA0ACgB7AA0ACgAgACAAIAAgAHAAdQBiAGwAaQBjACAAaQBuAHQAI
ABMAGUAZgB0ADsADQAKACAAIAAgACAAcAB1AGIAbABpAGMAIABpAG4AdAAgAFQAbwBwADsADQAKACAAIAAgACAAcAB1AGIAbABpAGMAIABpAG4AdAAgAFIAaQBnAGgAdAA7AA0ACgAgA
CAAIAAgAHAAdQBiAGwAaQBjACAAaQBuAHQAIABCAG8AdAB0AG8AbQA7AA0ACgB9AA0ACgAnAEAAIAAtAE4AYQBtAGUAIAAiAHQAeQBwAGUAJAAoAFsAZwB1AGkAZABdADoAOgBOAGUAd
wBHAHUAaQBkACgAKQAgAC0AcgBlAHAAbABhAGMAZQAgACcALQAnACkAIgAgAC0AUABhAHMAcwBUAGgAcgB1AA0ACgANAAoAJABNAHkAVwBpAG4AZABvAHcASABhAG4AZABsAGUAIAA9A
CAAKABHAGUAdAAtAFAAcgBvAGMAZQBzAHMAIAAtAEkAZAAgACgARwBlAHQALQBXAG0AaQBPAGIAagBlAGMAdAAgAFcAaQBuADMAMgBfAFAAcgBvAGMAZQBzAHMAIAAtAEYAaQBsAHQAZ
QByACAAIgBQAHIAbwBjAGUAcwBzAEkAZAA9ACQAUABJAEQAIgApAC4AUABhAHIAZQBuAHQAUAByAG8AYwBlAHMAcwBJAGQAKQAuAE0AYQBpAG4AVwBpAG4AZABvAHcASABhAG4AZABsA
GUADQAKAA0ACgAkAFcAaQBuAGQAbwB3AFIAZQBjAHQAIAA9ACAATgBlAHcALQBPAGIAagBlAGMAdAAgAC0AVAB5AHAAZQBOAGEAbQBlACAAJABSAGUAYwB0AGEAbgBnAGwAZQBTAHQAc
gB1AGMAdAAuAEYAdQBsAGwATgBhAG0AZQANAAoAJABuAHUAbABsACAAPQAgACQAVwBpAG4AZABvAHcARgB1AG4AYwB0AGkAbwBuADoAOgBHAGUAdABXAGkAbgBkAG8AdwBSAGUAYwB0A
CgAJABNAHkAVwBpAG4AZABvAHcASABhAG4AZABsAGUALABbAHIAZQBmAF0AJABXAGkAbgBkAG8AdwBSAGUAYwB0ACkADQAKAA0ACgBXAHIAaQB0AGUALQBIAG8AcwB0ACAAJABXAGkAb
gBkAG8AdwBSAGUAYwB0AC4ATABlAGYAdAAgACQAVwBpAG4AZABvAHcAUgBlAGMAdAAuAFQAbwBwACAAJABXAGkAbgBkAG8AdwBSAGUAYwB0AC4AUgBpAGcAaAB0ACAAJABXAGkAbgBkA
G8AdwBSAGUAYwB0AC4AQgBvAHQAdABvAG0A

100 -2 1257 748
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206