Моя цель - запустить стороннее приложение через оболочку 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
Привет @jdweng Может быть, я не совсем понял: PS на самом деле работает так, как ожидалось. Переименование работает и сохраняется. Однако переименование с кодом VB - нет.
Я знаю. Является ли проблема с файлами журнала логином или выходом? Использование Out-Null может помешать сообщению журнала содержать пароль.
Чтобы установить заголовок окна 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.
@EricvanLoon, как и где запустится консольное приложение? В ранее запущенной cmd.exe
сессии? Как бы вы это сделали, если бы не через команду запуска? Например. proc = Process.Start("cmd.exe", "/k title Test Text & som.exe foo bar")
Привет @mklement0 Я имел в виду, что в настоящее время я тестирую cmd.exe, но позже я буду использовать другое приложение (назовем его MyApp.exe), основанное на символах. Итак, моя программа VB запустит ее как Process.Start("MyApp.exe")
, и поэтому переключатель /k не будет работать. Извините, что не очень понятно.
@EricvanLoon, вы все еще можете вызывать это приложение через cmd.exe
, как в примере в моем предыдущем комментарии и как теперь также показано в обновленном ответе. Если вы не хотите, чтобы сеанс cmd.exe
оставался открытым, используйте /c
вместо /k
.
Привет @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")
Еще раз спасибо за вашу помощь!! С уважением, Эрик
Рад это слышать, @EricvanLoon. foo bar
— это просто примеры аргументов, переданных в образец исполняемого файла, some.exe
. Просто удалите их из своего звонка.
Облом, я просто плохо искал. :-( foo bar
вызывает сбой MyApp, который возвращает меня к окну cmd, в котором используется измененный заголовок. Как только я удаляю слова, MyApp успешно запускается, берет заголовок и заменяет его своим... Таким образом, заголовок работает только для самих окон cmd, а не для любого приложения, которое в нем запущено.
@ЭрикванЛун. Неважно SetConsoleTitle
- я не знал, что вы звоните из графического интерфейса. Тем не менее, комбинация подходов, описанных в ответе, кажется, работает: установите начальный заголовок через title
, затем в своем коде VB снова установите тот же заголовок после запуска вашего приложения, и, поскольку заголовки одинаковы, даже cmd.exe
возврат к заголовку окна при выборе окна, например, покажет желаемый заголовок.
Привет @mklement0 Прежде всего, большое спасибо за то, что подумали! Я добавил фрагмент кода к моему вопросу выше, чтобы показать вам, в чем проблема, когда вы используете SetWindowText для cmd. Когда форма загружается, она открывает окно cmd (с именем My new title) и запускает (просто в качестве примера) PowerShell в нем. В этот момент название меняется на My new title - powershell.exe
. Когда вы нажимаете кнопку Button1, заголовок снова меняется на My new title
, но если вы выбираете любой символ с помощью мыши, он снова меняется на My new title - powershell.exe
, поэтому он не будет прикрепляться. В очередной раз благодарим за помощь! Эрик
@EricvanLoon, посмотрите мое обновление, которое должно решить эту проблему. Тому, кто удалил мой первоначальный комментарий: это уместно сделать только после того, как адресат его увидит, после чего оба комментария должны быть удалены. Если адресат никогда не увидит комментарий об обновлении, он может так и не узнать, что проблема решена, а если останется только предыдущий комментарий, будущие читатели могут ошибочно предположить, что проблема все еще существует.
Привет @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
, когда вы щелкаете в любом месте окна.
@EricvanLoon, поэтому я изменил свой ответ, чтобы также показать решение /c
, в котором нет этой проблемы (я подумал, что будет достаточно попросить вас прочитать измененный ответ; я не думал, что мне нужно написать его по буквам здесь тоже).
Привет mklement0 Извините за путаницу и спасибо за терпение. С /c он действительно работает с PowerShell. Но для приложения, которое я хочу использовать в конце концов, это не так. Скорее всего потому, что это приложение загружается немного дольше, чем PowerShell, который каким-то образом отменяет изменения в команде title...
@EricvanLoon, я обновил ответ, чтобы показать решение, которое работает аналогично вашему подходу PowerShell: оно ждет в цикле, пока заголовок окна не изменится. Конечно, это предполагает, что ваше приложение устанавливает свой заголовок только один раз, при запуске.
Привет @mklement0, Еще раз большое спасибо за вашу помощь и пример кода. Я протестировал его с помощью различных инструментов (например, ping.exe), и он работает для всех приложений, но не для конкретного приложения, которое мне нравится использовать. Я думаю, что это приложение явно обновляет заголовок после каждого взаимодействия с пользователем, я не могу объяснить иначе ... Это все еще не объясняет, почему обновление заголовка является постоянным, когда оно выполняется через PowerShell Start-ThreadJob. Хотя это не работает для моего конкретного приложения, я отметил ваш ответ как ответ. Я очень ценю все время, которое вы потратили на ответ на мой вопрос! Эрик
С удовольствием, @EricvanLoon. Сожалеем, что он все еще не работает. Последнее, что вы можете попробовать, — это поддерживать цикл на стороне VB.NET до тех пор, пока работает приложение, и продолжать вызывать SetWindowText()
всякий раз, когда обнаруживается изменение в заголовке окна.
Привет @mklement0, я тоже думал об этом решении, но моя форма VB запускает несколько экземпляров приложения. Я не знаю, как сделать цикл переименования (который должен работать в фоновом режиме, пока эта копия приложения активна) и в то же время разрешить моей форме запускать новые экземпляры. Итак, моя форма содержит Button1 и Textbox1. Текстовое поле содержит параметры для приложения, и его новый экземпляр выполняется при нажатии кнопки Button1. Когда я использую цикл переименования, я не смогу запускать новые экземпляры, верно? Форма будет ждать завершения цикла? С уважением, Эрик
@EricvanLoon, вам нужно запустить отдельный поток, который отслеживает каждый запущенный экземпляр.
Часто вам приходится использовать Out-Null в PS для подавления вывода. Попробуйте добавить следующее: }-ArgumentList $host.ui.RawUI, $titletext | Out-Null