Проверьте, выполняется ли сценарий PowerShell с использованием пакетного сценария - PullRequest
1 голос
/ 06 октября 2019

Я запускаю сценарий powershell, используя командный файл, например:

@ECHO OFF
PowerShell.exe -Command "& 'U:\...\...\..._PS.ps1'"
PAUSE

Этот сценарий .ps1 может занять 15 минут. В течение этого времени я хочу избежать запуска этого скрипта еще раз. Если пользователь запускает файл .bat, я хочу, чтобы консоль выводила что-то вроде: «Сценарий Powershell уже запущен, подождите, пока он не закончится» *

Из различных примеров я видел этот код:

echo off
tasklist /fi "imagename eq powershell.exe" |find ":" > nul
if errorlevel 1 taskkill /f /im "powershell.exe"
exit

Есть 2 проблемы с ним: 1. Он работает только с файлами .exe, такими как блокнот, powershell и т. Д. У меня есть путь с .ps1 2. Это убивает задачу, мне нужно только показать сообщение

Ответы [ 5 ]

3 голосов
/ 06 октября 2019

Вы можете сделать что-то вроде следующего, если придерживаетесь параметра -Command:

powershell.exe -Command "if (((Get-CimInstance Win32_Process -Filter \"name = 'powershell.exe'\").CommandLine -match '..._PS\.ps1').Count -gt 1) {'Powershell is already running.'} else {& 'U:\...\...\..._PS.ps1'}"

Идея состоит в том, чтобы проверить Win32_Process элементы, запущенные в данный момент, которые имеют командную строку, включающую ваш..._PS.ps1 файл. Поскольку проверяющая команда добавит себя в стек перед проверкой условия, необходимо проверить наличие более 1 вхождения. Любые литеральные символы . должны быть экранированы обратной косой чертой при использовании оператора -match, поскольку -match использует регулярное выражение. Обратите внимание на обратную косую черту внутренних двойных кавычек, потому что вы запускаете это из оболочки CMD. Для запуска этого из PowerShell потребуются другие escape-символы.

2 голосов
/ 06 октября 2019

Возможно, вы могли бы использовать для проверки powershell.exe процесса со связанным CommandLine, оканчивающимся именем скрипта:

@(For /F "Delims==" %%A In (
    'WMIC Process Where "Name='powershell.exe' And CommandLine Like '%%\\unknown[_]PS.ps1\"'" Get Name /Value'
)Do @If "%%A"=="Name" (
    Echo "Powershell script is already running, wait until it's over"&Timeout 10 /NoBreak>NUL&Exit /B)) 2>NUL
@Powershell -ExecutionPolicy RemoteSigned -File "U:\...\...\unknown_PS.ps1"

Поскольку вы не предоставили полностьюуточните фактический путь к файлу и имя, вам нужно настроить этот скрипт самостоятельно. В строке 5 замените U:\...\...\unknown_PS.ps1 на фактический путь к файлу и, что важно, замените unknown в строке 2 фактическим префиксом _PS.ps1. Обратите внимание, что оператор Like использует символ подчеркивания в качестве символа подстановки, поэтому любые символы подчеркивания требуют экранирования, заключив их в квадратные скобки, (я уже сделал это для известного подчеркивания в _PS.ps1) . Я думаю, что достаточно проверить только командную строку, заканчивающуюся \unknown_PS.ps1", хотя вы можете расширить ее до полного пути, если вы ожидаете иметь более одного сценария powershell с тем же именем файла.

0 голосов
/ 07 октября 2019

Поскольку вы используете PowerShell, вы также можете создать общесистемный мьютекс в PowerShell, чтобы предотвратить одновременное выполнение нескольких экземпляров сценария.

@echo off
setlocal

:: This should be a predefined static unique value.
:: This is an example, you should generate your own GUID
set "MutexName=MyScript.PS1:{72a7654d-aa01-4e7e-a985-89add8524590}"
set "PSMsg=Powershell script is already running, wait until it''s over"

set "PSCode=[bool]$isOwner = $false; $mutex=[System.Threading.Mutex]::new($true , '%MutexName%', [ref]$isOwner)"
set "PSCode=%PSCode%; if($isOwner) {& '.\MyScript.ps1'} else {Write-Output '%PSMsg%'}; $mutex.close()"

PowerShell.exe -Command "iex ${env:PSCode}"

Если вы контролируетесодержимое скрипта PowerShell, то вы должны включить этот метод непосредственно в сам скрипт, а не оборачивать скрипт в другой код

0 голосов
/ 07 октября 2019

полезный ответ AdminOfThings и полезный ответ Compo оба предоставляют надежные решения .

Вот альтернативное решение, котороеработает, если вы готовы сделать одно предположение :

  • Если ваш пакет с именем, скажем, foo.cmd, найдет любой процесс cmd.exe, выполняющий пакетный файл с именем foo.cmd действительно выполняет тот же пакетный файл.

Решение использует два преимущества:

  • A cmd.exe В консольном окне добавляется имя / путь(в зависимости от специфики вызова) к заголовку окна ;например, если вы вызываете foo.cmd из текущего каталога, заголовок окна изменится на Command Prompt - foo.cmd.

  • tasklist.exe /v также выводит заголовки главного окна соответствующих процессов,

@echo off
setlocal

rem # Count the cmd.exe processes that have the name of this batch file
rem # in their window title.
for /f %%c in ('tasklist /v /fi "imagename eq cmd.exe" ^| find /C "%~nx0"') do (
  REM # If more than 1 process matches, another instance of this batch file
  REM # must already be running, so issue a warning and exit.
  if %%c GTR 1 echo Powershell script already running, wait for it to finish. >&2 & exit /b 1 
)

echo Running PowerShell script...

rem # Run the PowerShell script.
rem # As @Compo advises, it's better to use -File to invoke scripts
rem # and to use -ExecutionPolicy Bypass if needed (and you trust the script).
powershell.exe -file U:\path\to\your\PowerShellScript.ps1

pause

С большим усилием вы можете сделать решение более устойчивым :

  • Назначить уникальный заголовок пользовательского окна в текущем окне консоли.

  • Сначала проверьте cmd.exe процессов с этим заголовком: если найдено, выйдите;если нет, задайте уникальный заголовок окна и запустите сценарий PowerShell.

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

    • Проблема в том, что если кто-то прервет ваш командный файл с помощью Ctrl + C , вы не получитешанс сбросить заголовок.

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

@echo off
setlocal

rem # Get and save the current window title.
rem # Note: This - complex and somewhat slow - solution is necessary, 
rem #       because the `title` only supports *setting* a window title, 
rem #       not *getting it*.
for /f "usebackq delims=" %%t in (`powershell -noprofile -c "[Console]::Title.Replace(' - '+[Environment]::CommandLine,'') -replace '(.+) - .+', '$1'"`) do set origTitle=%%t

rem # Choose a unique window title.
set "titleWhileRunning=Some.ps1 is running..."

rem # If a cmd.exe process with the same window title already exists
rem # we issue a warning and exit
tasklist /v /fi "imagename eq cmd.exe" /fi "windowtitle eq %titleWhileRunning%" | find "%titleWhileRunning%" >NUL
if %ERRORLEVEL% EQU 0  echo Powershell script already running, wait for it to finish. >&2 & exit /b 1

rem # Set the new title that indicates that the script is running.
title %titleWhileRunning%

echo Running PowerShell script...

rem # Run the PowerShell script.
rem # As @Compo advises, it's better to use -File to invoke scripts
rem # and to use -ExecutionPolicy Bypass if needed (and you trust the script).
powershell.exe -file U:\path\to\your\PowerShellScript.ps1

rem # Restore the original window title.
title %origTitle%

pause
0 голосов
/ 06 октября 2019

Похоже, что одним из решений этой проблемы является использование механизма блокировки файлов от dbenham, который адаптирован к вашему коду, может выглядеть следующим образом:

@ECHO OFF
CALL :main >>"%~f0"
EXIT /B

:main
PowerShell.exe -Command "& 'U:\...\...\..._PS.ps1'"

EXIT /B

Тогда, если другойПроцесс пытается выполнить то же самое batch-file. Однако Powershell может выполнить .ps1. Следовательно, оптимальное решение в этом случае, вероятно, будет использовать тот же механизм внутри этого .ps1.

...