Страницы

воскресенье, 25 августа 2013 г.

Windows evermore.

Очередная вариация на тему forever для Windows. Карты на стол: открываю сакральную тайну постоянной подписки на события WMI, да простят меня посвященные :). Если серьезно, убежден, что в приведенном ниже коде каждый разработчик, заинтересованный в применении постоянной подписки найдет для себя что-нибудь вкусное.


' скрипт перманентного запуска процесса, 
' использует постоянную подписку на события WMI

' TODO:
' - класс в пространстве имен root\default для хранения информации об именах классов, 
' используемых для хранения информации о запущенных процессах, для того чтобы выводить список 
' процессов, запущенных с помощью скрипта, обращаться к процессам по индексу, 
' а не запоминать информацию о параметрах запуска скрипта
' - вынести логику выполнения скрипта в отдельный класс

errString = "Примеры: " & vbCrLf & _
  "- запуск notepad.exe:" & vbCrLf & _
  "  evermore.vbs notepad.exe" & vbCrLf & _
  "- запуск notepad.exe с выводом отладочных сообщений:" & vbCrLf & _
  "  evermore.vbs notepad.exe /v" & vbCrLf & _  
  "- запуск notepad.exe с определением имени фильтра, потребителя событий и класса:" & vbCrLf & _
  "  evermore.vbs notepad.exe /filter:filtername /consumer:consumername /class:classname" & vbCrLf & _  
  "- запуск notepad.exe на удаленном компьютере:" & vbCrLf & _
  "  evermore.vbs notepad.exe /computer:computername /user:username /pw:password" & vbCrLf & _
  "- завершние notepad.exe:" & vbCrLf & _
  "  evermore.vbs notepad.exe /q" & vbCrLf & _
  "- заверешние notepad.exe с выводом отладочных сообщений:" & vbCrLf & _
  "  evermore.vbs notepad.exe /q /v" & vbCrLf & _
  "- заверешние notepad.exe с определением имени фильтра, потребителя событий и класса:" & vbCrLf & _
  "  evermore.vbs notepad.exe /q /filter:filtername /consumer:consumername /class:classname" & vbCrLf & _
  "- получение журнала мониторинга, созданного с параметрами по умолчанию, в файл:" & vbCrLf & _
  "  evermore.vbs /r" & vbCrLf & _
  "- получение журнала мониторинга, созданного с параметрами по умолчанию, в окно консоли и в файл:" & vbCrLf & _
  "  evermore.vbs /r /v" & vbCrLf & _
  "- получение журнала мониторинга, созданного с параметрами по умолчанию, в окно консоли:" & vbCrLf & _
  "  evermore.vbs /r:0 /v"

Set args = WScript.Arguments

' что делать
todo = Wazzup(args)

If todo < 0 Then
 If todo = -1 Then WScript.Echo "Ошибка: недостаточно аргументов" & vbCrLf & errString
 If todo = -2 Then WScript.Echo "Ошибка: неверное количество аргументов" & vbCrLf & errString
 WScript.Quit
End if

' вывод отладочных сообщений
verbose = args.Named.Exists("v")

'------------------------------------------------
' подключаемся к WMI
Set objWMI = new WMI
If args.Named.Exists("computer") Then objWMI.Computer = args.Named.Item("computer")
If args.Named.Exists("user") Then objWMI.User = args.Named.Item("user")
If args.Named.Exists("pw") Then objWMI.PW = args.Named.Item("pw")
ret = objWMI.Connect()
If verbose Then WScript.Echo "Подключение к WMI: " & Not IsArray(ret)
If IsArray(ret) Then 
 WScript.Echo "Ошибка подключения к WMI" & vbCrLf & _
   "Код ошибки: " & ret(0) & vbCrLf & "Пространство имен: " & ret(1)
 WScript.Quit
End If

' проверяем регистрацию ActiveScriptEventConsumer
Set objSubscription = new Subscription
Set objSubscription.RootSubscription = objWMI.RootSubscription
Set objSubscription.RootCIMV2 = objWMI.RootCIMV2
ret = objSubscription.ScriptConsumerExists()
If verbose Then WScript.Echo "ActiveScriptEventConsumer зарегистрирован: " & CBool(ret)
If Not ret Then ' пытаемся зарегистрировать
 ret = objSubscription.ScriptConsumerReg()
 If verbose Then WScript.Echo "Регистрация ActiveScriptEventConsumer: " & CBool(ret)
 If Not ret Then
  WScript.Echo "Ошибка: не удалось зарегистрировать ActiveScriptEventConsumer"
  WScript.Quit
 End If
End If
'------------------------------------------------

If todo = 0 Then ' запускаем процесс не интерактивно 
 Set objConfig = objWMI.RootCIMV2.Get("Win32_ProcessStartup").SpawnInstance_
 objConfig.ShowWindow = 0 '0 - SW_HIDE, 1 - SW_NORMAL
 ret = objWMI.RootCIMV2.Get("Win32_Process").Create(args.Unnamed(0), , objConfig, pid)
 If verbose Then WScript.Echo "Процесс " & args.Unnamed(0) & " запущен: " & CBool(Not ret)
 If ret Then
  WScript.Echo "Ошибка: не удалось запустить процесс '" & args.Unnamed(0) & "'"
  WScript.Quit
 End If
End If

' создаем экземпляр объекта MyClass_
Set objMyClass = New MyClass_
Set objMyClass.RootDefault = objWMI.RootDefault
If args.Named.Exists("class") Then objMyClass.Name = args.Named.Item("class")
' проверяем наличие класса
exists = objMyClass.Exists()
If verbose Then WScript.Echo "Наличие класса " & objMyClass.Name & ": " & CBool(exists)

' если запускаем процесс - создаем класс в пространстве имен root\default для хранения информации
If todo = 0 Then
 ret = objMyClass.Create(args.Unnamed(0), pid)
 If verbose Then WScript.Echo "Создание класса " & objMyClass.Name & " в пространстве имен root\default: " & ret
 If Not ret Then 
  WScript.Echo "Ошибка: не удалось создать класс " & objMyClass.Name & " в пространстве имен root\default"
  WScript.Quit
 End If
ElseIf todo = 1 Then ' завершаем процесс 
 If Not exists Then
  WScript.Echo "Ошибка: класс " & objMyClass.Name & " не существует"  
 Else
  qpid = objWMI.RootDefault.Get(objMyClass.Name).PID
  If verbose Then WScript.Echo "Свойство PID класса " & objMyClass.Name & ": " & qpid
  Set colproc = objWMI.RootCIMV2.ExecQuery("SELECT * FROM Win32_Process WHERE ProcessID = '" & qpid & "'")
  gotpid = CBool(colproc.Count)
  If verbose Then WScript.Echo "Процесс PID = " & qpid & " обнаружен: " & gotpid
  If gotpid Then   
   ret = 0
   For Each proc In colproc
    ret = ret + proc.Terminate()
   Next
   If verbose Then WScript.Echo "Процесс PID = " & qpid & " завершен: " & Not CBool(ret)
   If ret Then WScript.Echo "Ошибка: не удалось завершить процесс PID = " & qpid
  End If  
 End If
 
 ret = objMyClass.Delete()
 If verbose Then WScript.Echo "Удаление класса " & objMyClass.Name & " в пространстве имен root\default: " & CBool(ret)
 If Not ret Then 
  WScript.Echo "Ошибка: не удалось удалить класс " & objMyClass.Name & " в пространстве имен root\default"
  WScript.Quit
 End If
ElseIf todo = 2 And IsEmpty(args.Named.Item("r")) Then ' читаем журнал, пишем в файл
 If Not exists Then
  WScript.Echo "Ошибка: класс " & objMyClass.Name & " не существует"  
 Else
  logName = "evermore.log"
  Set fso = CreateObject("Scripting.FileSystemObject")
  With fso.CreateTextFile(logName, True)
   With GetObject("winmgmts:\\.\root\default:" & objMyClass.Name)
    s = "Created: " & .Created & vbCrLf & "Process: " & .Process & vbCrLf & _
      "PID: " & .PID & vbCrLf & "Log: " & .Log
   End With
   .WriteLine(s)
   .Close
  End With  
  Set fso = Nothing
 End If
End If

If verbose Then 
 If exists And todo <> 1 Then ' если не завершаем процесс
  ' читаем свойства созданного класса
  With GetObject("winmgmts:\\.\root\default:" & objMyClass.Name)
  WScript.Echo "Свойства класса " & objMyClass.Name & ":" & vbCrLf & _
    "- Created: " & .Created & vbCrLf & "- Process: " & .Process & vbCrLf & _
    "- PID: " & .PID & vbCrLf & "- Log: " & .Log
  End With
 End If
End If

' создаем подписку
If args.Named.Exists("filter") Then objSubscription.Filter = args.Named.Item("filter")
If args.Named.Exists("consumer") Then objSubscription.Consumer = args.Named.Item("consumer")
If todo = 0 Then ' если запускаем процесс
 ret = objSubscription.Create(objMyClass.Name)
 If verbose Then WScript.Echo "Создание подписки: " & CBool(ret)
 If Not ret Then
  WScript.Echo "Ошибка: не удалось создать подписку"
  WScript.Quit
 End If
ElseIf todo = 1 Then ' если удаляем процесс
 ret = objSubscription.Delete()
 If verbose Then WScript.Echo "Удаление подписки: " & CBool(ret)
 If Not ret Then
  WScript.Echo "Ошибка: не удалось удалить подписку"
  WScript.Quit
 End If
End If

Set objMyClass = Nothing
Set objSubscription = Nothing
Set objWMI = Nothing


WScript.Echo "I gonna last evermore..."
WScript.Quit 0

' -1 - недостаточно аргументов, -2 - неверное количество аргументов, 
' 0 - запуск, 1 - завершение, 2 - чтение
Function Wazzup(args)
 If args.Unnamed.Count = 0 And Not args.Named.Exists("r") Then
  Wazzup = -1
  Exit Function
 ElseIf args.Named.Exists("q") And args.Named.Exists("r") Then
  Wazzup = -2
  Exit Function
 ElseIf Not(args.Named.Exists("q") Or args.Named.Exists("r")) Then
  Wazzup = 0
  Exit Function
 ElseIf args.Named.Exists("q") Then
  Wazzup = 1
  Exit Function
 ElseIf args.Named.Exists("r") Then
  Wazzup = 2
  Exit Function
 End If
End Function

' свойства: 
' RootDefault, RootCIMV2, RootSubscription - пространства имен WMI
' Computer, User, PW - данные для подключения к WMI удаленного компьютера
' методы: 
' Connect() - попытка подключения к WMI
' в случае ошибки возвращает массив: код ошибки и пространство имен, 
' при подключении к которому произошла ошибка
Class WMI
 Private m_Default, m_CIMV2, m_Subscription
 Private m_Computer, m_User, m_PW
 Private r1, r2, r3
 
  Public Property Get RootDefault()
  Set RootDefault = m_Default
  End Property
  Public Property Get RootCIMV2()
  Set RootCIMV2 = m_CIMV2
  End Property
  Public Property Get RootSubscription()
  Set RootSubscription = m_Subscription
  End Property
 
 Public Property Let Computer(i)
  m_Computer = i
 End Property
 Public Property Let User(i)
  m_User = i
 End Property
 Public Property Let PW(i)
  m_PW = i
 End Property
 
 Private Sub Class_Initialize()
  r1 = "root\default"
  r2 = "root\cimv2"
  r3 = "root\subscription"
 End Sub
 
 'подключение к пространствам имен 
  Function Connect()
  On Error Resume Next
    Set objLocator = CreateObject("WbemScripting.SWbemLocator")
    With objLocator.Security_
   .AuthenticationLevel = 6 'уровень секретности пакетов
   .ImpersonationLevel = 3 'объект-сервер может пользоваться всеми правами клиента
   .Privileges.Add(7) 'привилегия оператора безопасности
  End With
  
  Set m_Default = objLocator.ConnectServer(m_Computer, r1, m_User, m_PW, , , 128)
    If Err.Number <> 0 Then
      Connect = Array(Err.Number, r1)
   Exit Function
    End If
  
  Set m_CIMV2 = objLocator.ConnectServer(m_Computer, r2, m_User, m_PW, , , 128)
    If Err.Number <> 0 Then
      Connect = Array(Err.Number, r2)
      Exit Function
    End If
    
    Set m_Subscription = objLocator.ConnectServer(m_Computer, r3, m_User, m_PW, , , 128)
    If Err.Number <> 0 Then
      Connect = Array(Err.Number, r3)
      Exit Function
    End If
    Connect = 0  
 End Function

End Class

' свойства:
' Filter, Consumer - имена фильтра и потребителя событий, по умолчанию MyFilter и MyConsumer
' RootSubscription, RootCIMV2 - объекты RootSubscription и RootCIMV2, необходимо присвоить значения
' методы: 
' ScriptConsumerExists() - проверка регистрации ActiveScriptEventConsumer, 
' возвращает True если ActiveScriptEventConsumer зарегистрирован
' ScriptConsumerReg() - попытка регистрации ActiveScriptEventConsumer, 
' возвращает True в случае успешной регистрации ActiveScriptEventConsumer
' Create(с_) - создание подписки на событие, получает имя класса, в случае успеха возвращает True
' Delete() - удаление подписки на событие, в случае успеха возвращает True
Class Subscription 
 Private m_Filter, m_Consumer, m_Subscription, m_CIMV2
 Public Property Let Filter(i)
  m_Filter = i
 End Property
 Public Property Let Consumer(i)
  m_Consumer = i
 End Property
 Public Property Get Filter
  Filter = m_Filter
 End Property
 Public Property Get Consumer
  Consumer = m_Consumer
 End Property
 Public Property Set RootSubscription(i)
  Set m_Subscription = i
 End Property
 Public Property Set RootCIMV2(i)
  Set m_CIMV2 = i
 End Property
 
 Private Sub Class_Initialize()
  'значения по умолчанию
  m_Filter = "MyFilter"
  m_Consumer = "MyConsumer"
 End Sub
 
 'проверка регистрации стандартного потребителя ActiveScriptEventConsumer
 Function ScriptConsumerExists()
  On Error Resume Next  
  If m_Subscription.ExecQuery( _
    "SELECT * FROM __Provider WHERE Name='ActiveScriptEventConsumer'").Count Then
   ScriptConsumerExists = True
  End If
 End Function
 
 'регистрация стандартного потребителя
 Function ScriptConsumerReg()
  On Error Resume Next  
  'конфигурируем создание процесса
  Set objConfig = m_CIMV2.Get("Win32_ProcessStartup").SpawnInstance_
  objConfig.ShowWindow = 0 'SW_HIDE, 1 - SW_NORMAL
  'регистрация стандартного потребителя
  ret = m_CIMV2.Get("Win32_Process").Create( _
    "mofcomp -N:root\subscription %SystemRoot%\system32\Wbem\scrcons.mof", , objConfig, intProcessID)
  If ret <> 0 Then Exit Function
  'отправляем идентификатор процесса в функцию ожидания завершения процесса
  ScriptConsumerReg = ProcessFinished(intProcessID)
 End Function
    
 'ожидание завершения процесса "до упора"
 Function ProcessFinished(intProcessID)
  On Error Resume Next  
  Set colProcesses = m_CIMV2.ExecNotificationQuery( _
    "SELECT * FROM __InstanceDeletionEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_Process'" & _
    " AND TargetInstance.ProcessID = '" & intProcessID & "'")
  Do
   Set objProcess = colProcesses.NextEvent
   If Err.Number = 0 Then ProcessFinished = True
   Exit Function
  Loop
 End Function
 
 'создание подписки на событие
 Function Create(c_)
  On Error Resume Next
  sid = Array(1,2,0,0,0,0,0,5,32,0,0,0,32,2,0,0) ' sid администратора
  'создание фильтра события
    With m_Subscription.Get("__EventFilter").SpawnInstance_()
   .Name = m_Filter
      .QueryLanguage = "WQL"
      .EventNamespace = "root\cimv2"
      .Query = "SELECT * FROM __InstanceDeletionEvent WITHIN 2 " & _
     "WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.ProcessId = '" & pid & "'"
   .CreatorSID = sid
      Set objFilterPath = .Put_
    End With
    
  var = "Const c_ = """ & c_ & """, f_ = """ & m_Filter & """" & vbCrLf & "With GetObject(""winmgmts:\\.\root\default:"" & c_)" & vbCrLf & " .Log = .Log & vbCrLf & Now() & "" - Process '"" & .Process & ""' stopped, pid: "" & .PID" & vbCrLf & " ret = GetObject(""winmgmts:\\.\root\cimv2"").Get(""Win32_Process"").Create(.Process, , , pid)" & vbCrLf & " created = Not CBool(ret)" & vbCrLf & " If created Then" & vbCrLf & "  .PID = pid" & vbCrLf & "  .Log = .Log & vbCrLf & Now() & "" - Process '"" & .Process & ""' started, pid: "" & pid" & vbCrLf & " Else" & vbCrLf & "  .PID = vbNullString" & vbCrLf
  var = var & "  .Log = .Log & vbCrLf & Now() & "" - Fail starting process '"" & .Process & ""' started, pid: "" & pid" & vbCrLf & " End If" & vbCrLf & " .Put_" & vbCrLf & "End With" & vbCrLf & "If created Then " & vbCrLf & " Set colFilters = GetObject(""winmgmts:\\.\root\subscription"").ExecQuery( _" & vbCrLf & "   ""SELECT * FROM __EventFilter WHERE Name='"" & f_ & ""'"")" & vbCrLf & " If colFilters.Count Then " & vbCrLf & "  For Each objFilter In colFilters" & vbCrLf & "   With objFilter" & vbCrLf & "    .Query = ""SELECT * FROM __InstanceDeletionEvent WITHIN 2 "" & _" & vbCrLf
  var = var & "      ""WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.ProcessId = '"" & pid & ""'""" & vbCrLf & "    .CreatorSID = Array(1,2,0,0,0,0,0,5,32,0,0,0,32,2,0,0)    " & vbCrLf & "    .Put_    " & vbCrLf & "   End With   " & vbCrLf & "  Next" & vbCrLf & " End If " & vbCrLf & "End If"
  
  'создание потребителя события
    With m_Subscription.Get("ActiveScriptEventConsumer").SpawnInstance_()
   .Name = m_Consumer
      .ScriptingEngine = "VBScript"
      .KillTimeout = 10 'завершить выполнение через 10 секунд
      .ScriptText = var
   .CreatorSID = sid
      Set objConsumerPath = .Put_
    End With
    
    'связка фильтра и потребителя
    With m_Subscription.Get("__FilterToConsumerBinding").SpawnInstance_()
      .Filter = objFilterPath
      .Consumer = objConsumerPath
   .CreatorSID = sid
      .Put_
    End With  
  
  Set objFilterPath = Nothing
    Set objConsumerPath = Nothing
  
  If Err.Number = 0 Then Create = True  
 End Function
 
 'удаление подписки на событие
 Function Delete()
  On Error Resume Next
  'удаляем фильтр
    Set colFilters = m_Subscription.ExecQuery("SELECT * FROM __EventFilter WHERE Name='" & m_Filter & "'")
    If colFilters.Count Then
   For Each objFilter In colFilters    
        objFilter.Delete_ ' удаляем сам фильтр
      Next
    End If
    Set colFilters = Nothing
        
    'удаляем потребителя
    Set colConsumers = m_Subscription.ExecQuery("SELECT * FROM ActiveScriptEventConsumer WHERE Name='" & m_Consumer & "'")
    If colConsumers.Count Then
      For Each objConsumer In colConsumers
    objConsumer.Delete_
      Next
    End If
    Set colConsumers = Nothing
  If Err.Number = 0 Then Delete = True 
 End Function 
End Class

' свойства:
' Name - имя класса, по умолчанию MyClass, 
' RootDefault - объект RootDefault, необходимо присвоить значение
' методы:
' Exists() - проверка существования класса в пространстве имен root\default, 
' возвращает True если класс существует
' Create(proc, pid) - создание класса в пространстве имен root\default, 
' получает команду запуска и ID процесса, возвращает True в случае успеха
' Delete() - удаление класса из пространства имен root\default, 
' возвращает True в случае успеха
Class MyClass_
 Private m_Name, m_Default
 Public Property Let Name(i)
  m_Name = i
 End Property 
 Public Property Get Name
  Name = m_Name
 End Property
 Public Property Set RootDefault(i)
  Set m_Default = i
 End Property
 Private Sub Class_Initialize()
  m_Name = "MyClass" 'по умолчанию
 End Sub
 
 'наличие класса
 Function Exists()
  On Error Resume Next
    For Each objClass In m_Default.SubclassesOf()
   If InStr(objClass.Path_.Path, m_Name) Then    
    Exists = True
        Exit Function
      End If
    Next
 End Function
 
 'создаем класс
 Function Create(proc, pid)
  On Error Resume Next
    With m_Default.Get()
   .Path_.Class = m_Name
   .Properties_.Add "Log", 8 'журнал
   .Properties_.Add "Created", 8 'дата создания
   .Properties_.Add "PID", 8 'ID процесса
   .Properties_.Add "Process", 8 'команда запуска процесса
   .Properties_("Log") = vbNullString 'по умолчанию
   .Properties_("Created") = Now()
   .Properties_("PID") = pid
   .Properties_("Process") = proc
      .Put_ 'пишем класс в репозиторий
  End With  
  If Err.Number = 0 Then Create = True
 End Function
 
 'удаляем класс
 Function Delete()
  On Error Resume Next
  With m_Default.Get()
   .Path_.Class = m_Name
   .Put_
  End With
  m_Default.Delete m_Name
  If Err.Number = 0 Then Delete = True  
 End Function 
End Class

Как это работает.

Скрипт состоит из трех классов, отвечающих за подключение к WMI, создание постоянной подписки на события WMI и хранение информации в пространстве имен root\default, а также вступительной части, содержащей логику выполнения скрипта.

На самом деле большая часть кода (классы) мною написана уже давно, поэтому в данном случае мне оставалось написать в основном логику самого скрипта.

Скрипт подключается к WMI, создает постоянную подписку и класс в пространстве имен root\default для хранения информации, необходимой для работы скрипта.

Свойство ScriptText создаваемого потребителя событий ActiveScriptEventConsumer содержит следующий код:
Const c_ = "MyClass", f_ = "MyFilter"
With GetObject("winmgmts:\\.\root\default:" & c_)
 .Log = .Log & vbCrLf & Now() & " - Process '" & .Process & "' stopped, pid: " & .PID
 ret = GetObject("winmgmts:\\.\root\cimv2").Get("Win32_Process").Create(.Process, , , pid)
 created = Not CBool(ret)
 If created Then
  .PID = pid
  .Log = .Log & vbCrLf & Now() & " - Process '" & .Process & "' started, pid: " & pid
 Else
  .PID = vbNullString
  .Log = .Log & vbCrLf & Now() & " - Fail starting process '" & .Process & "' started, pid: " & pid
 End If
 .Put_
End With
If created Then 
 Set colFilters = GetObject("winmgmts:\\.\root\subscription").ExecQuery( _
   "SELECT * FROM __EventFilter WHERE Name='" & f_ & "'")
 If colFilters.Count Then 
  For Each objFilter In colFilters
   With objFilter
    .Query = "SELECT * FROM __InstanceDeletionEvent WITHIN 2 " & _
      "WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.ProcessId = '" & pid & "'"
    .CreatorSID = Array(1,2,0,0,0,0,0,5,32,0,0,0,32,2,0,0)    
    .Put_    
   End With   
  Next
 End If 
End If

Этот код сохраняем в файл (script.vbs), тестируем, после чего преобразуем в строку с помощью следующего скрипта (scr2var.vbs):
'*************************************
'создание переменной из текста скрипта
'*************************************
Set args = WScript.Arguments
If  args.Count = 0 Then
 WScript.Echo "Ошибка: не достаточно аргументов"
 WScript.Quit
End If

max = 9 ' максимальное количество строк файла в строке присваивания значения переменной, по умолчанию - 10
If args.Named.Exists("max") Then max = CInt(args.Named.Item("max")) + 1

Set fso = CreateObject("Scripting.FileSystemObject")
With fso
 If Not .FileExists(args(0)) Then
  WScript.Echo "Ошибка: файл " & args(0) & " не найден"
  WScript.Quit
 End If
 Set f1 = .OpenTextFile(args(0),1)
 f2Name = args(0) & ".txt"
 Set f2 = .CreateTextFile(f2Name,True)
End With

Set args = Nothing

f2.Write "var = " 'первая строка
i = 0
Do Until f1.AtEndOfStream 'читаем
 If i > max Then 'новая строка
  f2.Write "var = var & " & Chr(34) & Replace(Trim(f1.ReadLine),Chr(34),Chr(34) & Chr(34)) & Chr(34) & " & vbCrLf & "
  i = 0
 Else
  If i = max Then 'последняя строка
   f2.WriteLine Chr(34) & Replace(Trim(f1.ReadLine),Chr(34),Chr(34) & Chr(34)) & Chr(34) & " & vbCrLf"   
  Else 'обычная строка
   f2.Write Chr(34) & Replace(Trim(f1.ReadLine),Chr(34),Chr(34) & Chr(34)) & Chr(34)
   If Not f1.AtEndOfStream Then f2.Write " & vbCrLf & "
  End If
  i = i + 1
 End If 
Loop
f2.Close
f1.Close
Set f1 = Nothing
Set f2 = Nothing
Set fso = Nothing
WScript.Echo "Создание файла " & f2Name & " завершено"

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

Закидываем полученную строку в свойство ScriptText потребителя событий - метод Create(c_) класса Subscription - скрипт готов.

Запуск скрипта - от имени АДМИНИСТРАТОРА. Процесс запускается НЕ ИНТЕРАКТИВНО.
Подробности, примеры - в тексте скрипта.

Тестируем. У меня Windows 8 x64 со всеми параноидальными фичами безопасности.

Запускаем процесс.

Несколько раз убиваем процесс. Читаем журнал, сохраняем.

Останавливаем процесс.

Дисклеймер: за "допиливание" полученного кода с целью противозаконного использования автор ответственности не несет.