1

Тема: HTA & JScript: HTA silent stop and close - тихая остановка и закрытие

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

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

Каждый новый перезапуск обычного гибридного приложения представляет из себя его новый экземпляр из отдельного участка кода, помещенного в if-else.
Вложенность if-else требует предваряющего объявления глобальных переменных и функций за ее пределами, что может снижать производительность, если предыдущим экземплярам эти глобальные переменные были не нужны. Плюс появляется лишняя лесенка в самом коде.

Но можно ли вызвать код точечно без структуры "else" с более поздним объявлением глобальных переменных, свойственных только для текущего экземпляра, не обрабатывая участки кода, не предназначенные новому экземпляру?
Мой ответ - можно. Для этого нужно остановить скрипт предыдущего экземпляра перед выполнением "чужого" кода.

На практике такой подход используется в примерах "HTA & JScript: HTA as 64 bit in focus - запуск HTA как 64 bit в фокусе" и "HTA & JScript: Extended Drop Target - Юникод-приемник файлов и папок", рассмотренных на данном форуме.


Закрытие скрипта без пробегания последующих строк кода.

Включите звук в ваших колонках и запустите следующий скрипт в HTA:

<script>

close();
//onerror=function(){close();};throw 0;

close();
close();
for(var i=0;i<100;i++)
    close();
var a=0;
a++;
a++;
a++;
close();

alert('"Baby, can\'t you see?\n'+
'I\'m calling\n'+
'A guy like you\n'+
'Should wear a warning\n'+
'It\'s dangerous\n'+
'I\'m falling..."'+String.fromCharCode(169));

</script>

Вы услышите "динь" от "alert", завершаюшего код. И это после сотен вызовов "close"!

Кто-то из вас мог об этом и не подозревать, но глубокое исследование при включенном общении скриптов между собой подтвердило, что закрытие для браузера еще не означает буквально закрытие: при линейном вызове он пробегается вплоть до самой последней строки скрипта.
При такой пробежке могут возникать и тихо ловиться разнообразные ошибки, особенно если скрипт не был готов к пробегаемой части кода, а также осуществляются нежелательные инициализации, например, сторонних объектов, которые предназначались без использования вложенности "else" для другого экземпляра приложения.

И это поведение браузера по умолчанию! Мне удалось отменить такие попытки браузера, направив его в обработку ошибок.

Раскомментируйте вторую строку, и браузер покорно сделает, что от него требуется - закроет скрипт сразу после вызова, и "динь" больше не будет.
Это действенный метод для закрытия скрипта без пробегания последующих строк кода.


Остановка скрипта. Нюансы.

Характерной для WScript и CScript функции "Sleep" для JScript и VBScript в HTA нет, но есть "setTimeout", что является большой разницей, так как последующая часть кода без таймаута в HTA продолжит извлекаться.

Остановить скрипт совсем в HTA все же можно, например, модальным диалогом, и лучше скрытым. Сделать это можно так:

<script>
showModalDialog('javascript:setTimeout(function(){close();},3000);', 0, 'dialogWidth:0;unadorned:1;');
alert('I am shown in 3 seconds of waiting!');
</script>

Все хорошо, но есть один нюанс - что делать, если требуется продолжение или закрытие остановленного скрипта, но не по таймауту, который может самостоятельно контролировать модальный диалог, как показано в примере выше, а по определенному событию.
Исследование показывает, что общаться со скриптом модальный диалог не может.
На выручку приходит обмен объектами между скриптами от Xameleon.
Закрываем остановленный скрипт из другого окна, и на этом все. Как бы да, но нет.

Глубокое исследование показывает, что принудительное закрытие извне остановленного модальным диалогом скрипта приводит к нежелательному мерцанию плитки приложения на панели задач.
Что же там может мерцать, если, например, приложение и вовсе было заранее скрыто от показа на панели задач?
А мерцает спрятанный нами ранее модальный диалог, который как дикий зверь вырывается из под контроля на панель задач на какие-то миллисекунды во время принудительного закрытия родительского окна.
По всей видимости, для модального диалога такой случай не рассматривался разработчиками данного компонента как возможный.

Мне удалось докрутить решение до полноценного - договориться с модальным диалогом как с обычным окном, с которым также можно обменяться объектами.
После закрытия диалога извне основной скрипт разморозится, его выполнение продолжится, где далее нужно, как обычно, закрыть теперь уже сам скрипт методом "close" его же собственного окна и избавиться при необходимости от пробегания последующих строк кода рассмотренным выше способом.

А вот еще одна задача - как сделать так, чтобы скрипт был остановлен на выполнение последующих строк кода, но при этом сработал таймаут для другой функции и, что сложнее, без обмена объектами между скриптами?
Увы, простой предварительный вызов "setTimeout" не сработает вовремя: скрипт при такой постановке задачи оказывается заморожен раньше этого события.
Я нашел для решения такой задачи следующий способ - достаточно вызвать "setTimeout" в другом "потоке", например, со стороны псевдоокна ActiveX объекта "htmlfile":

<script>
setTimeout(function(){alert('I am shown after all other messages!');}, 1000);

GetObject('\\','htmlfile').parentWindow.setTimeout(function(){alert('I am shown in 2 seconds of waiting!');}, 2000);

showModalDialog('javascript:setTimeout(function(){close();},3000);', 0, 'dialogWidth:0;unadorned:1;');
alert('I am shown in 3 seconds of waiting!');
</script>

Итоги. После разбора данной темы мы умеем:

  • закрывать скрипт без пробегания последующих строк кода;

  • приостанавливать скрипт по собственному таймауту;

  • приостанавливать скрипт до наступления события извне;

  • вызывать методы остановленного скрипта без обмена объектами между скриптами.

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

Решение смотрим ниже.


Скриптовый пример имеет следующие специальные возможности:

+ открыть спойлер
  • не требует установки какого-либо стороннего программного обеспечения и поддерживает любую версию Windows, начиная с XP (возможно и 2000) с определенными ActiveX объектами, не запрещенными по умолчанию;

  • полностью написан на JScript в файле скриптового формата HTA и использует следующие ActiveX объекты: WScript.Shell, Scriptlet.TypeLib, Shell.Application, htmlfile и Shell.Explorer.2, также известный как Microsoft Web Browser;

  • тестировался и адаптирован для Windows XP, Windows 7 и Windows 10;

  • не пишет каких-либо записей в реестр и не сохраняет каких-либо персональных данных на вашем жестком диске;

  • не выходит в сеть каким-либо способом;

  • получает тихую остановку скрипта в HTA и корректно закрывает отложенный скрипт.


Скриптовый пример демонстрирует:

  • как скрыть HTA во время его динамического создания;

  • как получить тихую остановку скрипта в HTA;

  • как корректно закрыть отложенный скрипт

  • и как решить подтвержденные проблемы с немедленным вызовом "moveTo" или "resizeTo" при старте в Windows XP.


Работа с исходным кодом.

  1. Для запуска скриптового примера скопируйте его исходный код и сохраните его в текстовом файле, сменив его расширение на "hta".

  2. Запуск скриптового примера осуществляется двойным нажатием, как и в случае с обычной программой.

  3. Перед запуском скрипта закройте или сверните все лишние окна, чтобы увидеть скриптовые сообщения. Для упрощения примера фокусировка окон в нем не предусмотрена.

Рекомендую использовать AkelPad для просмотра сохраненного исходного кода примера с правильными отступами.

Вы можете использовать скриптовый пример на свое усмотрение, но, пожалуйста, оставьте комментарий с прямой ссылкой на tastyscriptsforfree.wix.com/page/scripts в этом случае.

<script>
/*
HTA silent stop and close v1.0.1 (h t t p s://tastyscriptsforfree.wix.com/page/scripts)
Copyright 2017-2020 Vladimir Samarets. All rights reserved.
tastyscriptsforfree@protonmail.com

Release date: November 1, 2020.


Use this script sample entirely at your own risk.
This script sample is copyrighted freeware and I am not responsible for any damage or data loss it could unintentionally cause.
You may modify it but please leave a comment with direct link to https://tastyscriptsforfree.wix.com/page/scripts in that case.


*******
The purpose of this script sample:
the script sample is intended for demonstration of how to obtain silent stop for script in HTA and how to close delayed script gracefully.

*******
The script sample has the following special features:

it requires no third party software installation and supports any Windows version since XP with certain ActiveX objects not restricted by default;
it is entirely written in JScript in HTA scripting file format and uses the following ActiveX objects: WScript.Shell, Scriptlet.TypeLib, Shell.Application, htmlfile and Shell.Explorer.2
    also known as Microsoft Web Browser;
it was tested and adapted for Windows XP, Windows 7 and Windows 10;
it writes no registry values and stores no personal settings on your hard disk;
it doesn't access network in any way;
it obtains silent stop for script in HTA and closes delayed script gracefully.

*******
The script sample demonstrates:

how to hide HTA while having it dynamically created;
how to obtain silent stop for script in HTA;
how to close delayed script gracefully;
and how to deal with confirmed immediate "moveTo" or "resizeTo" issues at application start in Windows XP.

*******
Basic documentation and articles I used:

'WSH: exchanging data and objects between scripts - 2' by Xameleon (March, 2011) in Russian
    (h t t p://forum.script-coding.com/viewtopic.php?id=5573);

'InternetExplorer and WebBrowser objects' by Ludogovskiy Aleksander in Russian
    (h t t p s://script-coding.com/WSH/WebBrowser.html);

'Windows Script 5.6 Documentation' (script56.chm) by Microsoft Corporation
    (h t t p s://w w w.microsoft.com/en-us/download/confirmation.aspx?id=2764);
'MSDN' related documentation by Microsoft Corporation
    (h t t p s://docs.microsoft.com/en-us/);

Windows XP moveTo and resizeTo issues:
'"Access is denied" by executing .hta file with JScript on Windows XP x64' discussion on stackoverflow.com (January 21, 2009)
    (h t t p s://stackoverflow.com/questions/464679/access-is-denied-by-executing-hta-file-with-jscript-on-windows-xp-x64);
'Something strange with HTAs' discussion on social.technet.microsoft.com (September 19, 2011)
    (h t t p s://social.technet.microsoft.com/Forums/officeocs/en-US/92bf1e76-ebd5-4462-bd52-533e69305a5c/something-strange-with-htas?forum=ITCG).

*******
Basic software I used:

AkelPad 4.9.8 by Aleksander Shengalts and Alexey Kuznetsov (as development environment)
    (h t t p://akelpad.sourceforge.net/en/download.php);
OLE/COM Object Viewer v2.10.059 (oleview.exe) by Charlie Kindel, Michael Nelson, and Michael Antonio (for documentation purposes).
*/

document.title='I am the MASTER script!';
var ID = new ActiveXObject('Scriptlet.TypeLib').GUID.substr(0, 38);     //the unique ID obtaining
document.documentElement.firstChild.insertAdjacentHTML('afterBegin', '<object id=' + ID +
    ' classid=clsid:8856F961-340A-11D0-A96B-00C04FD705A2><param name=RegisterAsBrowser value=1>');      //registering current HTA window in collection of windows

alert('Wait for 3 seconds till the OUTER script is started.');
GetObject('\\','htmlfile').parentWindow.setTimeout      //a timeout here is to show the master script is stopped long before the "The outer script wants me to close now." message appears
(
    function()                                                                   //the outer script will be executed with a timeout on behalf of the "htmlfile" while the master script is being in stopped state
    {
        new ActiveXObject('WScript.Shell').Run('mshta "about:<title>I am the OUTER script!<\/title><body>I want the MASTER script to close.</body><script>'+
            '!function hide(e){try{moveTo(10000,10000);}catch(e){try{hide();}catch(e){hide();}}}();resizeTo(400,100);moveTo(screen.availWidth/2-200,screen.availHeight/2-200);'+
            'for(var ws=new ActiveXObject(\"Shell.Application\").Windows(),i=ws.Count;i-->0;)if((w=ws.Item(i))&&w.id==\"'+ID+'\"){setTimeout(function(){w.s.close();},3000);break;}<\/script>"');
    }
    ,
    3000
);

showModalDialog('javascript:for(var ws=new ActiveXObject("Shell.Application").Windows(),i=ws.Count;i-->0;)if((w=ws.Item(i))&&w.id=="'+ID+'"){w.s=document.Script;break;}', 0,
'dialogWidth:0;unadorned:1;');                                     //silent stop of the script

close(alert('The OUTER script wants me to close now.\n\nI am going to close myself gracefully without any further attempts of executing the rest statements of my code.'));

onerror=function(){close();};throw 0;                             //declining any further attempts of executing the rest statements of the code
alert("You won't hear my sound or see me if previous code line is present.");

</script>