1

My project is based in CMD. I am using a PS1 script for the desired effect of disabling the CMD windows X (close) button as I am doing DISM work with the cmd script and it will cause issues if the script is closed before it completes.

I am using the below PS code:

$code = @'
using System;
using System.Runtime.InteropServices;

namespace CloseButtonToggle {

 internal static class WinAPI {
   [DllImport("kernel32.dll")]
   internal static extern IntPtr GetConsoleWindow();

   [DllImport("user32.dll")]
   [return: MarshalAs(UnmanagedType.Bool)]
   internal static extern bool DeleteMenu(IntPtr hMenu,
                          uint uPosition, uint uFlags);

   [DllImport("user32.dll")]
   [return: MarshalAs(UnmanagedType.Bool)]
   internal static extern bool DrawMenuBar(IntPtr hWnd);

   [DllImport("user32.dll")]
   internal static extern IntPtr GetSystemMenu(IntPtr hWnd,
              [MarshalAs(UnmanagedType.Bool)]bool bRevert);

   const uint SC_CLOSE     = 0xf060;
   const uint MF_BYCOMMAND = 0;

   internal static void ChangeCurrentState(bool state) {
     IntPtr hMenu = GetSystemMenu(GetConsoleWindow(), state);
     DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
     DrawMenuBar(GetConsoleWindow());
   }
 }

 public static class Status {
   public static void Disable() {
     WinAPI.ChangeCurrentState(false); //its 'true' if need to enable
   }
 }
}
'@

Add-Type $code
[CloseButtonToggle.Status]::Disable()

My current method is to ECHO the above code to a PS1 file from within my CMD script, then run and delete it, e.g.:

CALL :DISABLEX
PAUSE
EXIT
:DISABLEX
(
ECHO $code = @'
ECHO using System;
ECHO using System.Runtime.InteropServices;
ECHO namespace CloseButtonToggle {
ECHO internal static class WinAPI {
ECHO [DllImport^("kernel32.dll"^)]
ECHO internal static extern IntPtr GetConsoleWindow^(^);
ECHO [DllImport^("user32.dll"^)]
ECHO [return: MarshalAs^(UnmanagedType.Bool^)]
ECHO internal static extern bool DeleteMenu^(IntPtr hMenu,
ECHO uint uPosition, uint uFlags^);
ECHO [DllImport^("user32.dll"^)]
ECHO [return: MarshalAs^(UnmanagedType.Bool^)]
ECHO internal static extern bool DrawMenuBar^(IntPtr hWnd^);
ECHO [DllImport^("user32.dll"^)]
ECHO internal static extern IntPtr GetSystemMenu^(IntPtr hWnd,
ECHO [MarshalAs^(UnmanagedType.Bool^)]bool bRevert^);
ECHO const uint SC_CLOSE     = 0xf060;
ECHO const uint MF_BYCOMMAND = 0;
ECHO internal static void ChangeCurrentState^(bool state^) {
ECHO IntPtr hMenu = GetSystemMenu^(GetConsoleWindow^(^), state^);
ECHO DeleteMenu^(hMenu, SC_CLOSE, MF_BYCOMMAND^);
ECHO DrawMenuBar^(GetConsoleWindow^(^)^);
ECHO }
ECHO }
ECHO public static class Status {
ECHO public static void Disable^(^) {
ECHO WinAPI.ChangeCurrentState^(false^);
ECHO }
ECHO }
ECHO }
ECHO '@
ECHO Add-Type $code
ECHO [CloseButtonToggle.Status]::Disable^(^)
)>"%~dp0disablex.ps1"
powershell -executionpolicy unrestricted -file "%~dp0disablex.ps1">>"%~dp0WimFix.log"
DEL "%~dp0disablex.ps1">>"%~dp0WimFix.log"
exit /b

It works, however I would prefer to just run it as a Powershell command from a single line instead of generating a ps1 file, e.g.:

powershell -nop -c 'command here; \"parsed with semicolons\"; but everything on one long line'

I'm not sure if this is possible, but after a few failed attempts to convert it to a command, I thought I'd ask for help before I give up and just continue the way I am already using it.

  • Is there any reason that this .ps1 script file could not be installed with the application? Is there a need to "hide" it? – lit Jun 24 '23 at 18:27
  • 2
    You might convert everything to [base64](https://en.wikipedia.org/wiki/Base64) and then use the [`-EncodedCommand](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_powershell_exe#-encodedcommand-base64encodedcommand) parameter, see [Pass complex arguments to powershell script through encoded command](https://stackoverflow.com/a/72882657/1701026) – iRon Jun 24 '23 at 18:38
  • @lit no need to hide it, but i dont like creating extra files to execute if i can avoid it – PlayLORD-SysOp Jun 25 '23 at 00:28
  • @iRon very good idea, fortunately I was very lucky and mklement0 hooked it up with a perfect one liner ;P I will look at that as an option in the future, although I will always prefer readable code. – PlayLORD-SysOp Jun 25 '23 at 00:30

1 Answers1

2

The following, which passes the code as a single-line string to -c (-Command), works:

powershell -nop -c "(Add-Type -PassThru 'using System; using System.Runtime.InteropServices; namespace CloseButtonToggle { internal static class WinAPI { [DllImport(\"kernel32.dll\")] internal static extern IntPtr GetConsoleWindow(); [DllImport(\"user32.dll\")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool DeleteMenu(IntPtr hMenu, uint uPosition, uint uFlags); [DllImport(\"user32.dll\")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool DrawMenuBar(IntPtr hWnd); [DllImport(\"user32.dll\")] internal static extern IntPtr GetSystemMenu(IntPtr hWnd, [MarshalAs(UnmanagedType.Bool)]bool bRevert); const uint SC_CLOSE = 0xf060; const uint MF_BYCOMMAND = 0; internal static void ChangeCurrentState(bool state) { IntPtr hMenu = GetSystemMenu(GetConsoleWindow(), state); DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND); DrawMenuBar(GetConsoleWindow()); } } public static class Status { public static void Disable() { WinAPI.ChangeCurrentState(false); } } }')[-1]::Disable()"

The -c argument was constructed from your multi-line code as follows:

  • The single-line equivalent of your $code string value - the argument passed to Add-Type's (positionally implied) -TypeDefinition parameter - was created as follows - note the need to escape the embedded " chars. as \":

    $code -replace '\s+', ' ' -replace '"', '\"'
    
    • Since $code contains C# code, in which statements are unambiguously demarcated (terminated) with ;, this transformation is unproblematic - assuming that no // comments are present, which is why //its 'true' if need to enable was removed first.
  • The Add-Type call was streamlined with the -PassThru switch, which outputs the compiled types, and allows invoking members on them directly, if the Add-Type call is enclosed in (...)

    • Since the call outputs two types, the internal WinAPI class and the public Status, in order, [-1] is used to reference the latter and call ::Disable() on it.
mklement0
  • 382,024
  • 64
  • 607
  • 775