Изменение заголовка окна cmd (VB.net против PowerShell)

Моя цель - запустить стороннее приложение через оболочку cmd. Моя программа VB будет запускать несколько экземпляров, и мне нравится устанавливать заголовок cmd, чтобы отслеживать эти несколько окон. Я столкнулся со следующей проблемой: когда я меняю заголовок с помощью VB, изменение не соответствует. Новый заголовок возвращается к заголовку по умолчанию, как только вы используете функцию копирования/вставки в этом окне или щелкаете в любом месте окна cmd. Вот код VB, который я использую:

Imports System.Threading
Public Class Form1
    Private Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" (ByVal hwnd As Integer, ByVal lpString As String) As Integer
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim h_wnd As Integer
        Dim proc As New Process
        proc = Process.Start("cmd.exe")
        Thread.Sleep(2000)
        h_wnd = proc.MainWindowHandle
        SetWindowText(h_wnd, "Test Text")
    End Sub
End Class

Когда я делаю то же самое через PowerShell, переименование выполняется последовательно. Вот код PS, который я использую

Add-Type -Type @"
using System;
using System.Runtime.InteropServices;
namespace WT {
   public class Temp {
      [DllImport("user32.dll")]
      public static extern bool SetWindowText(IntPtr hWnd, string lpString); 
   }
}
"@

$titletext = "Test Text"
    # Start a thread job to change the window title to $titletext
    $null = Start-ThreadJob { param( $rawUI, $windowTitle )
    Start-Sleep -s 2
    if ( $rawUI.WindowTitle -ne $windowTitle ) {
        $rawUI.WindowTitle = $windowTitle
    }
    }-ArgumentList $host.ui.RawUI, $titletext
echo $rawUI
    & 'C:\Windows\System32\cmd.exe'

Проблема в том, что я не смогу использовать PowerShell, потому что часть параметров, проанализированных в сценарии, является паролем, а PowerShell регистрирует все записи в журнале Windows Powershell, включая пароль. Я не могу объяснить, почему изменение названия сохраняется в PS и почему его нет в VB. У кого-нибудь есть идея? Спасибо за любую помощь заранее!

С уважением, Эрик

Добавлен дополнительный код, чтобы привести пример моей проблемы с переименованием в VB:

Imports System.Threading
Public Class Form1
    Private Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" (ByVal hwnd As Integer, ByVal lpString As String) As Integer
    Public Shared proc As New Process
    Public Shared h_wnd As Integer
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        proc = Process.Start("cmd.exe", "/k title My new title & powershell.exe")
        Thread.Sleep(2000)
    End Sub
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        h_wnd = proc.MainWindowHandle
        SetWindowText(h_wnd, "My new title")
    End Sub
End Class

Часто вам приходится использовать Out-Null в PS для подавления вывода. Попробуйте добавить следующее: }-ArgumentList $host.ui.RawUI, $titletext | Out-Null

jdweng 09.02.2023 15:24

Привет @jdweng Может быть, я не совсем понял: PS на самом деле работает так, как ожидалось. Переименование работает и сохраняется. Однако переименование с кодом VB - нет.

Eric van Loon 09.02.2023 16:11

Я знаю. Является ли проблема с файлами журнала логином или выходом? Использование Out-Null может помешать сообщению журнала содержать пароль.

jdweng 09.02.2023 17:19
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
134
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы установить заголовок окна cmd.exe один раз при запуске:

Вы можете использовать интерфейс командной строки cmd.exe и его внутреннюю команду title, чтобы постоянно устанавливать заголовок нового окна консоли:

proc = Process.Start("cmd.exe", "/k title Test Text")
  • /k создает интерактивный cmd.exe сеанс, который остается открытым и принимает команду запуска (напротив, /c выполняет заданную команду запуска, а затем завершает работу — см. cmd /?)

  • title устанавливает заголовок окна; не заключайте аргумент в "...", потому что тогда " сохранится в заголовке. Если ваш заголовок содержит cmd.exe метасимволы (кроме пробелов), например &, экранируйте их по отдельности с помощью ^.

  • Заголовок, установленный таким образом, остается в силе, пока он не будет перезаписан консольным приложением, вызванным из сеанса во время работы этого приложения.

    • Если консольное приложение не изменяет заголовок, cmd.exe добавляет командную строку вызова к своему заголовку на время выполнения (например, Test Text - some.exe foo bar), но только в интерактивном сеансе, т. е. запущенном с помощью cmd /k (плюс команда запуска) или без аргументов; напротив, этого не происходит с cmd /c.

    • В любом случае после выхода из приложения исходное название восстанавливается.


Если вам нужно отменить эффекты изменения заголовка, выполненного консольным приложением во время его работы, то асинхронный подход кажется единственным вариантом, которого нельзя достичь с помощью одного cmd.exe, потому что он не поддерживает фоновые задания.

  • Оба устанавливают исходный заголовок окна с помощью title и ваш асинхронный подход VB.NET к вызову SetWindowText() более поздних действий необходим, потому что, когда вы используете SetWindowText() для изменения заголовка окна консоли позже, cmd.exe не понимает, что это произошло, и возвращается к своему заголовку при взаимодействии с окном, например, при выборе текст, который вы видели.

  • Вы можете сделать свой подход SetWindowText() более детерминированным, ожидая в цикле, пока заголовок фактически не изменится со своего начального значения, а затем сделать вызов, аналогичный тому, что делает ваш код PowerShell, только вне процесса.

Потому что, как отмечалось выше, использование /k заставляет cmd.exe добавлять командную строку вызова к заголовку своего окна, пока запущена программа, запущенная из него, так что при взаимодействии с окном вы увидите не просто Test Text, а что-то вроде Test Text - some.exe foo bar.
Поэтому вызовите с помощью cmd /c и выполните вложенный вызов cmd /k после выполнения вашего консольного приложения, чтобы затем войти в интерактивный сеанс; например: (some.exe — пример имени исполняемого файла, а foo и bar — примеры аргументов — адаптируйте по мере необходимости):

Process.Start("cmd.exe", "/c title Test Text & some.exe foo bar & cmd /k")

В качестве альтернативы, если вы просто хотите держать окно открытым до тех пор, пока пользователь не нажмет клавишу (т. е. если вам не нужен интерактивный сеанс cmd.exe после завершения выполнения), замените cmd / k на pause; или просто опустите последнюю команду, если окну вообще не нужно оставаться открытым после закрытия.

Чтобы собрать все это воедино (опять же, измените образец вызова some.exe по мере необходимости):

Imports System.Threading
Public Class Form1
    Private Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" (ByVal hwnd As Integer, ByVal lpString As String) As Integer
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim proc As New Process
        ' Note: special chars. such as "&", ">", "|", ... would require "^"-escaping
        Dim customTitle As String = "Test Text"
        proc = Process.Start("cmd.exe", "/c title " & customTitle & " & some.exe foo bar & cmd /k")
        ' Wait until the console application has changed the window title.
        Do
          ' Note: It is assumed that 300 msecs. are enough for the intitial `title` command to take effect.
          Thread.Sleep(300)
        Loop Until Not proc.MainWindowTitle.StartsWith(customTitle)
        ' The console application has changed the window title,
        ' change it back.
        SetWindowText(proc.MainWindowHandle, customTitle)
    End Sub
End Class

Примечание:

  • Вы можете захотеть реализовать тайм-аут для цикла, который ожидает изменения заголовка окна, чтобы вы не застряли в бесконечном цикле, если, например, ваше консольное приложение не запустится.

  • Предполагается, что ваше консольное приложение меняет заголовок окна только один раз при запуске.

    • Обновление: вы сообщаете, что это, по-видимому, неправда, поэтому вам понадобится цикл, который отслеживает изменения заголовков на протяжении всего срока службы консольного приложения, что, в свою очередь, требует создания выделенного потока, который выполняет этот мониторинг, чтобы не заблокировать ваш графический интерфейс.

    • Непонятно почему, но вы заявляете, что с вашим подходом PowerShell достаточно однократного восстановления исходного заголовка (в этом случае вы правильно устанавливаете заголовок окна консоли из-за запуска внутри консоли); поэтому проще всего придерживаться вашего подхода PowerShell, проблемы с регистрацией паролей которого вы можете избежать, указав пароль через переменную среды, которую вы должны сначала установить в своем приложении VB.NET, как обсуждалось в этом ответе для вашего последующего вопрос вверх.

Привет @mklement0, в моем случае это не сработает. Мое приложение VB (позже в будущем) запустит внешнее (символьное) приложение, которое будет работать в окне cmd. В этом случае переключатель /k больше не будет работать, поскольку он действителен только для cmd.exe.

Eric van Loon 09.02.2023 17:01

@EricvanLoon, как и где запустится консольное приложение? В ранее запущенной cmd.exe сессии? Как бы вы это сделали, если бы не через команду запуска? Например. proc = Process.Start("cmd.exe", "/k title Test Text & som.exe foo bar")

mklement0 09.02.2023 17:13

Привет @mklement0 Я имел в виду, что в настоящее время я тестирую cmd.exe, но позже я буду использовать другое приложение (назовем его MyApp.exe), основанное на символах. Итак, моя программа VB запустит ее как Process.Start("MyApp.exe"), и поэтому переключатель /k не будет работать. Извините, что не очень понятно.

Eric van Loon 10.02.2023 15:08

@EricvanLoon, вы все еще можете вызывать это приложение через cmd.exe, как в примере в моем предыдущем комментарии и как теперь также показано в обновленном ответе. Если вы не хотите, чтобы сеанс cmd.exe оставался открытым, используйте /c вместо /k.

mklement0 10.02.2023 15:16

Привет @mklement0 Кажется, твоя команда действительно работает! Я не знаю, что именно делает этот foo bar, единственная проблема сейчас в том, что эти слова также анализируются в моем приложении, что приводит к ошибке. Но название изменено и осталось, так что здесь большой прогресс! Мое приложение MyApp использует два параметра: -id=userid -pa=пароль, поэтому синтаксис теперь такой: Process.Start("cmd.exe", "/k title Test Text & c:\MyApp\MyApp.exe -id=readonly -pass=readonly foo bar") Еще раз спасибо за вашу помощь!! С уважением, Эрик

Eric van Loon 10.02.2023 16:04

Рад это слышать, @EricvanLoon. foo bar — это просто примеры аргументов, переданных в образец исполняемого файла, some.exe. Просто удалите их из своего звонка.

mklement0 10.02.2023 16:07

Облом, я просто плохо искал. :-( foo bar вызывает сбой MyApp, который возвращает меня к окну cmd, в котором используется измененный заголовок. Как только я удаляю слова, MyApp успешно запускается, берет заголовок и заменяет его своим... Таким образом, заголовок работает только для самих окон cmd, а не для любого приложения, которое в нем запущено.

Eric van Loon 10.02.2023 16:23

@ЭрикванЛун. Неважно SetConsoleTitle - я не знал, что вы звоните из графического интерфейса. Тем не менее, комбинация подходов, описанных в ответе, кажется, работает: установите начальный заголовок через title, затем в своем коде VB снова установите тот же заголовок после запуска вашего приложения, и, поскольку заголовки одинаковы, даже cmd.exe возврат к заголовку окна при выборе окна, например, покажет желаемый заголовок.

mklement0 10.02.2023 17:11

Привет @mklement0 Прежде всего, большое спасибо за то, что подумали! Я добавил фрагмент кода к моему вопросу выше, чтобы показать вам, в чем проблема, когда вы используете SetWindowText для cmd. Когда форма загружается, она открывает окно cmd (с именем My new title) и запускает (просто в качестве примера) PowerShell в нем. В этот момент название меняется на My new title - powershell.exe. Когда вы нажимаете кнопку Button1, заголовок снова меняется на My new title, но если вы выбираете любой символ с помощью мыши, он снова меняется на My new title - powershell.exe, поэтому он не будет прикрепляться. В очередной раз благодарим за помощь! Эрик

Eric van Loon 13.02.2023 11:36

@EricvanLoon, посмотрите мое обновление, которое должно решить эту проблему. Тому, кто удалил мой первоначальный комментарий: это уместно сделать только после того, как адресат его увидит, после чего оба комментария должны быть удалены. Если адресат никогда не увидит комментарий об обновлении, он может так и не узнать, что проблема решена, а если останется только предыдущий комментарий, будущие читатели могут ошибочно предположить, что проблема все еще существует.

mklement0 13.02.2023 19:08

Привет @mklemet0 Вы сказали set the initial title via title, then, in your VB code, set that same title again after your application has launched, and since the titles are the same Это то, что делает мое приложение (см. пример, который я добавил к своему первоначальному вопросу). Он запускает приложение (в моем примере powershell) в переименованном окне: Process.Start("cmd.exe", "/k title My new title & powershell.exe") и после этого я снова переименовываю его через кнопку: SetWindowText(h_wnd, "My new title"). Но имя меняется обратно на My new title - powershell.exe, когда вы щелкаете в любом месте окна.

Eric van Loon 14.02.2023 10:25

@EricvanLoon, поэтому я изменил свой ответ, чтобы также показать решение /c, в котором нет этой проблемы (я подумал, что будет достаточно попросить вас прочитать измененный ответ; я не думал, что мне нужно написать его по буквам здесь тоже).

mklement0 14.02.2023 14:20

Привет mklement0 Извините за путаницу и спасибо за терпение. С /c он действительно работает с PowerShell. Но для приложения, которое я хочу использовать в конце концов, это не так. Скорее всего потому, что это приложение загружается немного дольше, чем PowerShell, который каким-то образом отменяет изменения в команде title...

Eric van Loon 14.02.2023 16:11

@EricvanLoon, я обновил ответ, чтобы показать решение, которое работает аналогично вашему подходу PowerShell: оно ждет в цикле, пока заголовок окна не изменится. Конечно, это предполагает, что ваше приложение устанавливает свой заголовок только один раз, при запуске.

mklement0 14.02.2023 17:31

Привет @mklement0, Еще раз большое спасибо за вашу помощь и пример кода. Я протестировал его с помощью различных инструментов (например, ping.exe), и он работает для всех приложений, но не для конкретного приложения, которое мне нравится использовать. Я думаю, что это приложение явно обновляет заголовок после каждого взаимодействия с пользователем, я не могу объяснить иначе ... Это все еще не объясняет, почему обновление заголовка является постоянным, когда оно выполняется через PowerShell Start-ThreadJob. Хотя это не работает для моего конкретного приложения, я отметил ваш ответ как ответ. Я очень ценю все время, которое вы потратили на ответ на мой вопрос! Эрик

Eric van Loon 15.02.2023 16:14

С удовольствием, @EricvanLoon. Сожалеем, что он все еще не работает. Последнее, что вы можете попробовать, — это поддерживать цикл на стороне VB.NET до тех пор, пока работает приложение, и продолжать вызывать SetWindowText() всякий раз, когда обнаруживается изменение в заголовке окна.

mklement0 15.02.2023 16:53

Привет @mklement0, я тоже думал об этом решении, но моя форма VB запускает несколько экземпляров приложения. Я не знаю, как сделать цикл переименования (который должен работать в фоновом режиме, пока эта копия приложения активна) и в то же время разрешить моей форме запускать новые экземпляры. Итак, моя форма содержит Button1 и Textbox1. Текстовое поле содержит параметры для приложения, и его новый экземпляр выполняется при нажатии кнопки Button1. Когда я использую цикл переименования, я не смогу запускать новые экземпляры, верно? Форма будет ждать завершения цикла? С уважением, Эрик

Eric van Loon 15.02.2023 17:15

@EricvanLoon, вам нужно запустить отдельный поток, который отслеживает каждый запущенный экземпляр.

mklement0 15.02.2023 17:19

Другие вопросы по теме