1

Тема: WSH: Как прервать работу скрипта внутри ScriptControl?

Приветствую! В коде использую ScriptControl, который выполняет разные рутинные задачи, в связи с чем появился вопрос: как мне организовать выход из скрипта, который выполняется в ScriptControl, - так называемый аналог WScript.Quit()?

Ну, если интересно, зачем мне это вдруг понадобилось, то причина следующая:
Цель проекта -  удаленное исполнение скриптов (администрирование на базе WSH) через сеть "Интернет". Проект делится на серверную и клиентскую часть.
Клиентская часть (скрипт), установленная на клиенте, периодически обращается по 80 порту к интернет-ресурсу, который в случае необходимости выдает скриптовый код на JS либо VBS, который необходимо выполнить на машине. По результатам работы кода отправляется отчет об успехе работы исполненного кода на сервер (метод post от аякса).
Так как не хочется плодить кучу процессов, а также есть желание получать отчеты об ошибках вплоть до строки и столбца скрипта (при том условии обеспечения бесперебойного выполнения скрипта), то тут как раз и может пригодиться ScriptControl. Однако его выбор несет ряд недостатков:
1. Отсутствие поддержки WScript, что влечет за собой несовместимость либо уязвимость устойчивости работы основного скрипта.
2. Пока не получилось осуществить выход из дочернего скрипта (конструкции типа break label меня пугают).
С учетом того, что мне хочется осуществить полную совместимость с WSH и в одном процессе, то задача становится сложной.

Если есть идеи, то буду раз услышать:)

2

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

JSman, я не очень понял про среду исполнения. Какова она будет?

3

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

alexii пишет:

JSman, я не очень понял про среду исполнения. Какова она будет?

Среда исполнения Windows Script Host.

var SC = WScript.CreateObject("ScriptControl");

SC.Language = "VBScript";

var _WScript = {
    Name : WScript.Name,
    FullName : WScript.FullName, 
    Version : WScript.Version,
    BuildVersion : WScript.BuildVersion,
    ScriptName : WScript.ScriptName, 
    ScriptFullName : WScript.ScriptFullName, 
    StdIn : WScript.StdIn, 
    StdOut : WScript.StdOut, 
    StrErr : WScript.StdErr, 
    Arguments : {},
    Interactive : WScript.Interactive,
    
    Sleep : function (m) {return WScript.Sleep(m)},
    Quit : function () {},
    Echo : function (Msg) 
    {
        WScript.Echo(Msg)
    },
    CreateObject : function (ProgId, Pref) {return WScript.CreateObject(ProgId, Pref);},
    ConnectObject : function (Moniker) {return WScript.ConnectObject(Moniker);}, 
    DisconnectObject : function (a) {return WScript.DisconnectObject(a)}, 
    GetObject : function (Moniker) {return WScript.GetObject(Moniker);}
}
SC.AddObject("WScript", _WScript);
SC.AddObject("MsgBox", _WScript.Echo);
SC.AddCode("MsgBox 123 \n WScript.Quit\nWScript.Echo 123 ");

То есть вопрос такой: как мне реализовать WScript.Quit()?

4

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

JSman, я про что — тогда, может быть, использовать формат *.wsf? Клиент получает код, сохраняет его во временный файл, запускает шаблонный *.wsf, который содержит ссылку на этот временный файл:

…
<script language="VBScript" src="bla-bla-bla">
</script>
…

Или такое не гоже?

5

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

alexii, скорее всего нет, так как создается отдельный процесс.

6

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

Можно вот такой костыль попробовать:

var SC = WScript.CreateObject("ScriptControl");

var _WScript = {
    Name : WScript.Name,
    FullName : WScript.FullName, 
    Version : WScript.Version,
    BuildVersion : WScript.BuildVersion,
    ScriptName : WScript.ScriptName, 
    ScriptFullName : WScript.ScriptFullName, 
    StdIn : WScript.StdIn, 
    StdOut : WScript.StdOut, 
    StrErr : WScript.StdErr, 
    Arguments : {},
    Interactive : WScript.Interactive,
    
    Sleep : function (m) {return WScript.Sleep(m)},
    Quit : function ( num ) {throw new Error( num, 'Quit')},
    Echo : function (Msg) 
    {
        WScript.Echo(Msg)
    },
    CreateObject : function (ProgId, Pref) {return WScript.CreateObject(ProgId, Pref);},
    ConnectObject : function (Moniker) {return WScript.ConnectObject(Moniker);}, 
    DisconnectObject : function (a) {return WScript.DisconnectObject(a)}, 
    GetObject : function (Moniker) {return WScript.GetObject(Moniker);}
}

function Test( Code ){
  try{
    SC.AddCode( Code );
  }catch( E ){
    if( E.description == 'Quit')
      WScript.Echo('Exit code: ' + E.number );
  }
}

SC.Language = "VBScript";
SC.AddObject("WScript", _WScript);
SC.AddObject("MsgBox", _WScript.Echo);

WScript.Echo('\n\nTest VB 1');

Test( ' MsgBox 123 \n WScript.Quit \nWScript.Echo "12345 (must not see this)"');

WScript.Echo('\n\nTest VB 2 (without On Error Goto 0)');

Test( ' On Error Resume Next \n MsgBox 123 \n MsgBox 1/0 \n MsgBox 1234 \n WScript.Quit\nWScript.Echo "12345 (must not see this)" ');

WScript.Echo('\n\nTest VB 3');

Test( ' On Error Resume Next \n MsgBox 123 \n MsgBox 1/0 \n MsgBox 1234 \nOn Error Goto 0 \n WScript.Quit\nWScript.Echo "12345 (must not see this)" ');


SC.Reset();

SC.Language = "JScript";
SC.AddObject("WScript", _WScript);
SC.AddObject("MsgBox", _WScript.Echo);


WScript.Echo('\n\nTest JS 1');

Test( ' WScript.Echo( 123 ); \n WScript.Quit(); \nWScript.Echo( "12345 (must not see this)");');

WScript.Echo('\n\nTest JS 2');

Test( ' WScript.Echo( 123 ); \n WScript.Quit( 5 ); \nWScript.Echo( "12345 (must not see this)");');


WScript.Echo('\nEnd');

Только, имхо, для продакшина надо выбрасывать не new Error, а завести свой класс и в catch проверять его по конструктору
Из минусов - присылаемые скрипты всё-таки должны быть адаптированы под удалённый запуск.
В VBS, если используется "On Error Resume Next" нужно чтобы перед "WScript.Quit" делалось "On Error Goto 0".
В JS во всех try{} catch нужно проверять, не пришло ли исключение выхода и выбрасывать его дальше.

P.S. На windows 7 x64 с IE8 у меня на первую строку ругается - "1.js(1, 1) WScript.CreateObject: Не удается создать объект с именем "ScriptControl".". Не знаю - это от  windows 7 или от x64, но имхо проверять придётся на разных платформах.

7

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

JSman пишет:

как мне реализовать WScript.Quit()?

Вы же его почти написали

JSman пишет:
    Sleep : function (m) {return WScript.Sleep(m)},
    Quit : function () {},
    Echo : function (Msg) 
    {
        WScript.Echo(Msg)
    },

Что-то не работает? Или Quit вызванного скрипта не выполняет выход из вызывающего? Я попробовал, работает: скрипт выводит только одну строку сообщения, два других - нет. Или я не до конца понял задачу?

var WScript_ = {
    Quit: function(exitCode)
    {
        WScript.Quit(exitCode);
    }, 
    Echo: function(msg)
    {
        WScript.Echo(msg);
    }
};

var vb = new ActiveXObject('MSScriptControl.ScriptControl');
vb.language = 'VBScript';
vb.AddObject('WScript', WScript_);

var code = [
    'WScript.Echo "To be or not to be"', 
    'WScript.Quit', 
    'WScript.Echo "ORLY!"', 
].join('\n');

try {
    vb.AddCode(code);
} catch (e) {
    WScript.Echo(vb.Error.Line);
    WScript.Echo(vb.Error.Description);
}

WScript.Echo('ORLY!');
( 2 * b ) || ! ( 2 * b )

8

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

Spy00000 пишет:
    if( E.description == 'Quit')
      WScript.Echo('Exit code: ' + E.number );

У Вас очень много информации выводится за один раз. Оставьте только один тест и Вы увидите, что исключение, брошенное в Quit не доходит до обработчика. Уберите проверку, оставьте только вывод номера ошибки и добавьте вывод описания ошибки. Вы сразу поймете. Кстати номер ошибки выводите в 16-ричном виде - тогда будет видно, что он равен 0x80020101 (недокументированная ошибка). Описание здесь - http://support.microsoft.com/kb/247784/en-us

( 2 * b ) || ! ( 2 * b )

9

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

Rumata пишет:

Оставьте только один тест и Вы увидите, что исключение, брошенное в Quit не доходит до обработчика.

Если запускаемый скрипт на JS написан, тогда до внешнего скрипта исключение доходит. Из VBS - нет, это я видел. Имхо, главное - что внутренний скрипт прерывается в любом месте и выходит во внешний. Мне интересней - можно ли для VBS в создаваемый WScript.Quit вставить "On Error Goto 0" так, чтобы он на внутренний VBS действовал. У меня пока не получилось.

10

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

Для JS проблему решил использованием меток. Думаю, аналогичным подходом можно решить проблему для VBS.

this.FSO = fso = WScript.CreateObject("Scripting.FileSystemObject");

function LogError(e)
{
    WScript.Echo(e);
}

function isFile(Path)
{
    return fso.FileExists(Path);
}

function ReadTextFile(Path)
{
    if (!Path) return "";
    if (!isFile(Path)) return "";
    if (FSO.GetFile(Path).Size==0) return "";

    try 
    {
        var Stream = FSO.OpenTextFile(Path, 1, 0),
        Result = Stream.ReadAll();
        Stream.Close();
        return Result;
    }
    catch (e) {LogError(["ReadTextFile", Path].join("\t"))};
}


this.SC = WScript.CreateObject("ScriptControl");

var _WScript = {
    Name : WScript.Name,
    FullName : WScript.FullName, 
    Version : WScript.Version,
    BuildVersion : WScript.BuildVersion,
    ScriptName : WScript.ScriptName, 
    ScriptFullName : WScript.ScriptFullName, 
    StdIn : WScript.StdIn, 
    StdOut : WScript.StdOut, 
    StrErr : WScript.StdErr, 
    Arguments : {},
    Interactive : WScript.Interactive,
    
    Sleep : function (m) {return WScript.Sleep(m)},
    Quit : function () {},
    Echo : function (Msg) 
    {
        WScript.Echo(Msg)
    },
    CreateObject : function (ProgId, Pref) {return WScript.CreateObject(ProgId, Pref);},
    ConnectObject : function (Moniker) {return WScript.ConnectObject(Moniker);}, 
    DisconnectObject : function (a) {return WScript.DisconnectObject(a)}, 
    GetObject : function (Moniker) {return WScript.GetObject(Moniker);}
}

var SC = WScript.CreateObject("ScriptControl");

if (WScript.Arguments.Length==0) WScript.Quit(); var Code = ReadTextFile(WScript.Arguments.Item(0));
//var Code = "MsgBox(123);\n WScript.Quit();\nWScript.Echo(123); ";

SC.Language = "JScript";
SC.Reset();
SC.AddObject("WScript", _WScript);
SC.AddObject("MsgBox", _WScript.Echo);

if (SC.Language=="JScript") 
{
    Code = "quit:{"+Code.replace(/WScript\.Quit\([^)]*\)/gi, " break quit;")+"}";
    Code = Code.replace(/WScript.CreateObject\(([^,)]+),([^)]+)\)/gi, "new ActiveXObject($1)");
}

WScript.Echo(Code);

try
{
SC.AddCode(Code);
}
catch (e) 
{
    if (SC.Error)
    WScript.Echo([Code, SC.Error.Line, SC.Error.Column, SC.Error.Description].join("\r\n"));
}

Кстати, методы WScript.CreateObject, WScript.ConnectObject(Moniker), WScript.DisconnectObject(a), WScript.GetObject(Moniker) могут вести себя некорректно. Так, например, если подключиться к событиям объекта через WScript.CreateObject с указанием префикса, то события отлавливаться не будут.

Rumata пишет:

Или я не до конца понял задачу?

Смысл задачи в том, как завершить работу ScriptControl.

11 (изменено: Spy00000, 2011-12-12 08:28:46)

Re: WSH: Как прервать работу скрипта внутри ScriptControl?


quit:{
  function test_func(){
    WScript.Echo( 'test str 1');
    break quit;
    WScript.Echo( 'test str 2 (must not see this)');
  }
  WScript.Echo( 'str 1');
  test_func();
//  break quit;
  WScript.Echo( 'str 2 (must not see this)');
}

У меня на такую конструкцию ругается:
"1.js(4, 15) Ошибка компиляции Microsoft JScript: Метка не найдена"

Т.е. если WScript.Quit будет внутри функции - то скрипт просто не запустится.

12

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

Точно. Использование break  допустимо вне функций.

13

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

Решил перечитать тему внимательнее. Некоторые моменты показались спорными.

JSman пишет:

Клиентская часть ... обращается по 80 порту к интернет-ресурсу, который ... выдает скриптовый код на JS либо VBS, который необходимо выполнить на машине.

Насколько удаленный сервер безопасен? Известно, что нельзя доверять данным, пришедшим извне, тем более по широкодоступным средствам связи.

JSman пишет:

есть желание получать отчеты об ошибках вплоть до строки и столбца скрипта

К чему необходимость в такой детализации части кода, вызвавшего ошибку? Так ли важно, что ошибка произошла в 24 символе 143 строки, если ошибка уже произошла и надо проверять логику алгоритма, исправлять код программы и снова тестировать?

JSman пишет:

Пока не получилось осуществить выход из дочернего скрипта

В данном случае, как мне кажется, надо искать не обходные способы выхода из дочернего процесса, а разработать спецификацию для скриптов, которые будут выполняться под ScriptControl. В этой спецификации стоит особо оговорить способ завершения исполняемого скрипта (обычный выход, выход с возвратом кода завершения. аварийный выход с учетом завершения по прерыванию). И естественно в дочернем скрипте запретить команды вида WScript.Quit. И с исключениями надо быть очень осторожным - брошенные исключения ведут себя по-разному в зависимости от контекста исполнения - основная программа или подпрограмма. В большинстве случаев программные исключения не отлавливаемые либо не идентифицируемые.

( 2 * b ) || ! ( 2 * b )

14

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

Rumata пишет:

Насколько удаленный сервер безопасен? Известно, что нельзя доверять данным, пришедшим извне, тем более по широкодоступным средствам связи.

В настоящий момент пока это неважно. Сервер управляется мной, доступ к нему имею только я.

Rumata пишет:

К чему необходимость в такой детализации части кода, вызвавшего ошибку? Так ли важно, что ошибка произошла в 24 символе 143 строки, если ошибка уже произошла и надо проверять логику алгоритма, исправлять код программы и снова тестировать?

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

Rumata пишет:

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

Окей, получается, что предварительно, перед использованием, код необходимо
1) проверить на наличие объекта WScript (исключением является WScript.Sleep()),
2) если язык используется JS, то написать паттерны, позволяющие осуществить выход из кода в нужно точке. Пример такого паттерна:

(function(){
MY-CODE
}).call(this);

Выход реализуется ключевым словом return.

А стоит ли включать в код заранее подготовленные функции типа Download(Url, Destination), Run(Cmd)? Ведь был бы удобный функционал типа Run(Download(www.site.ru/InstallPluIn.exe, "%TMP%\123.exe"))

Еще вопрос: В случае запуска скриптов отдельным процессом, я предусматриваю использование GlobalObject для того чтобы в режиме реального времени принимать логи другого процессе. Допустимо ли это? Не нарушит ли это стабильность работы приложения?
 
Коллеги, может быть мне стоит создать отдельную тему с полным описанием?

15

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

JSman пишет:

Коллеги, может быть мне стоит создать отдельную тему с полным описанием?

Отчего нет?!

16

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

JSman пишет:

2) если язык используется JS, то написать паттерны, позволяющие осуществить выход из кода в нужно точке. Пример такого паттерна:

(function(){
MY-CODE
}).call(this);

Выход реализуется ключевым словом return.

Только если код будет вида

(function(){
...
  function MyFunc(){
    ...
    WScript.Quit();
    ...
  }
...
}).call(this);

замена "WScript.Quit();" на "return;" приведёт к выходу только из внутренней функции.

17

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

У меня два вопроса.

1. Может быть стоит подумать в сторону Run, Exec внешних скриптов? Тогда не надо будет ломать голову над тем как безболезненно выйти из дочернего процесса при этом сохранив работоспособность родительского. А эту тему и код, написанный по случаю использовать для других нужд.?

2. По теме наваял эдакий класс, который суть обертка вокруг вызова методов ScriptControl. Фактически, ООП-вариация первоначального кода коллеги JSman. Делиться?

( 2 * b ) || ! ( 2 * b )

18

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

Коллеги, в общем, предлагаю подытожить относительно выхода из ScriptControl, чтобы добавить что-то свежее и полезное в коллекцию, а тему, связанную с удаленным управлением, предлагаю обсудить тут.

19

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

Написал вот такой код для JS. Вроде бы все корректно. WScript.Quit() работает даже при использовании обработчика ошибок try-catch.

var SC = WScript.CreateObject("ScriptControl");
 
var _WScript = {
    Name : WScript.Name,
    FullName : WScript.FullName, 
    Version : WScript.Version,
    BuildVersion : WScript.BuildVersion,
    ScriptName : WScript.ScriptName, 
    ScriptFullName : WScript.ScriptFullName, 
    StdIn : WScript.StdIn, 
    StdOut : WScript.StdOut, 
    StrErr : WScript.StdErr, 
    Arguments : {},
    Interactive : WScript.Interactive,
 
    Sleep : function (m) {return WScript.Sleep(m)},
    Quit : function ( num ) {_WScript.Host.codeerr=num;_WScript.Host.mustQuit=true;throw new Error( num, 'Quit')},
    Echo : function (Msg) 
    {
        WScript.Echo(Msg)
    },
    CreateObject : function (ProgId, Pref) {return WScript.CreateObject(ProgId, Pref);},
    ConnectObject : function (Moniker) {return WScript.ConnectObject(Moniker);}, 
    DisconnectObject : function (a) {return WScript.DisconnectObject(a)}, 
    GetObject : function (Moniker) {return WScript.GetObject(Moniker);}
}
 
function Test( Code ){
  try{
    _WScript.Host = SC.CodeObject;
    Code = 'host=this;mustQuit=false;codeerr=0; function quit(){if (mustQuit){throw new Error(host.codeerr,"Quit")}}\n'+ Code.replace(/(catch *\([\$a-z_0-9]+[ \t\r\n]*\)[ \t\r\n]*\{)/gi, "$1quit();");   
SC.AddCode(Code);
  }catch( E ){
    if( E.description == 'Quit')
      WScript.Echo('Exit code: ' + E.number);
  }
}

SC.Language = "JScript";
SC.AddObject("WScript", _WScript);
SC.AddObject("MsgBox", _WScript.Echo);
  
WScript.Echo('\n\nTest JS 1');

Test( 'WScript.Echo( 123 ); \n try{try {WScript.Quit();} catch (e) {};}catch(q){}; \nWScript.Echo( "12345 (must not see this)");');
 
WScript.Echo('\n\nTest JS 2');
 
Test( ' WScript.Echo( 123 ); \n WScript.Quit( 5 ); \nWScript.Echo( "12345 (must not see this)");');
 
WScript.Echo('\nEnd');

20

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

Rumata пишет:

У меня два вопроса.

1. Может быть стоит подумать в сторону Run, Exec внешних скриптов? Тогда не надо будет ломать голову над тем как безболезненно выйти из дочернего процесса при этом сохранив работоспособность родительского. А эту тему и код, написанный по случаю использовать для других нужд.?

2. По теме наваял эдакий класс, который суть обертка вокруг вызова методов ScriptControl. Фактически, ООП-вариация первоначального кода коллеги JSman. Делиться?

Начну со второго вопроса.
2. Если есть в нем какой-то определенный смысл, то, конечно, делитесь. Я думаю, что это здравая идея представить это отдельным классом. Но может быть до использования класса еще подумаем над корректной поддержкой работы методов WScript (ConnectObject, CreateObject и др.), для того чтобы не пришлось сильно коверкать его структуру в процессе разработки?

1. Что касается первого вопроса, то буду рад услышать Ваши мысли вот в этой теме.

21

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

Spy00000, а что касается версии для vbs, достаточно ли просто сделать аналогичную вставку "On Error Goto 0" до "WScript.Quit" для корректной работы?

22 (изменено: Spy00000, 2011-12-22 17:47:47)

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

JSman пишет:

Spy00000, а что касается версии для vbs, достаточно ли просто сделать аналогичную вставку "On Error Goto 0" до "WScript.Quit" для корректной работы?

Я думаю - да, НО я на VBS почти не пишу, в основном готовые куски кода в WSH вставляю, чтобы на JS не переписывать. Так что могу ошибаться.

23

Re: WSH: Как прервать работу скрипта внутри ScriptControl?

Коллега Xameleon предложил такое решение для VBS:

    Quit : function () {if (SC.Language=="VBScript") 
SC.ExecuteStatement("On Error Goto 0: Err.Raise 1");},