none
über TaskScheduler gestartetes Script kann keine Scripte starten RRS feed

  • Frage

  • Moin!

    Ich habe diese zwei simplen Powershell-Scripte. s1.ps1:

    Function LogWrite($LogData)  {
        $LogMsg=(Get-Date -Format "yyyy.MM.dd HH:mm:ss") + " " + $LogData
        Add-content "c:\Users\dede\s1.log" -Value $LogMsg
    }
    
    LogWrite([Environment]::UserName)
    Start-Process -FilePath "powershell.exe" -ArgumentList "-file", "C:\Users\dede\s2.ps1"

    und s2.ps1:

    Function LogWrite($LogData)  {
        $LogMsg=(Get-Date -Format "yyyy.MM.dd HH:mm:ss") + " " + $LogData
        Add-content "c:\Users\dede\s2.log" -Value $LogMsg
    }
    
    LogWrite([Environment]::UserName)

    Nun starte ich das s1.ps1 als Administrator über den TaskScheduler.

    Der Job hat folgenden Inhalt (nach Exportieren):

    <?xml version="1.0" encoding="UTF-16"?>
    <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
      <RegistrationInfo>
        <Date>2016-12-15T09:11:52.8740749</Date>
        <Author>WIN-2I9GJLF4IE4\Administrator</Author>
      </RegistrationInfo>
      <Triggers>
        <TimeTrigger>
          <Repetition>
            <Interval>PT2M</Interval>
            <StopAtDurationEnd>false</StopAtDurationEnd>
          </Repetition>
          <StartBoundary>2016-12-15T09:00:00</StartBoundary>
          <Enabled>true</Enabled>
        </TimeTrigger>
      </Triggers>
      <Principals>
        <Principal id="Author">
          <UserId>WIN-2I9GJLF4IE4\Administrator</UserId>
          <LogonType>Password</LogonType>
          <RunLevel>LeastPrivilege</RunLevel>
        </Principal>
      </Principals>
      <Settings>
        <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
        <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
        <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
        <AllowHardTerminate>true</AllowHardTerminate>
        <StartWhenAvailable>false</StartWhenAvailable>
        <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
        <IdleSettings>
          <StopOnIdleEnd>true</StopOnIdleEnd>
          <RestartOnIdle>false</RestartOnIdle>
        </IdleSettings>
        <AllowStartOnDemand>true</AllowStartOnDemand>
        <Enabled>true</Enabled>
        <Hidden>false</Hidden>
        <RunOnlyIfIdle>false</RunOnlyIfIdle>
        <DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
        <UseUnifiedSchedulingEngine>false</UseUnifiedSchedulingEngine>
        <WakeToRun>false</WakeToRun>
        <ExecutionTimeLimit>P3D</ExecutionTimeLimit>
        <Priority>7</Priority>
      </Settings>
      <Actions Context="Author">
        <Exec>
          <Command>powershell.exe</Command>
          <Arguments>-file c:\users\dede\s1.ps1</Arguments>
        </Exec>
      </Actions>
    </Task>

    Erstmal funktioniert alles wie erwartet.

    Nach einem Reboot und Logon als Benutzer "dede" (nicht Administrator!) wird zwar s1.ps1 noch alle zwei Minuten gestartet, nicht jedoch s2.p1 aus s1.ps1 heraus.

    Wie erreiche ich, dass s2.ps1 aus s1.ps1 heraus gestartet werden kann, auch wenn der Benutzer, in dessen Kontext s1.ps1 abläuft, nicht angemeldet ist?

    Vielen Dank im voraus,

    Detlev


    • Bearbeitet dahlgrimm Donnerstag, 15. Dezember 2016 08:37 kleine Korrektur
    Donnerstag, 15. Dezember 2016 08:35

Antworten

  • Folgende Version von s1.ps1 funktioniert:

    Function LogWrite($LogData)  {
        $LogMsg=(Get-Date -Format "yyyy.MM.dd HH:mm:ss") + " " + $LogData
        Add-content "c:\Users\dede\s1.log" -Value $LogMsg
    }
    
    LogWrite([Environment]::UserName)
    Start-Process -FilePath "powershell.exe" -ArgumentList "C:\Users\dede\s2.ps1" -Wait
    
    Will heißen: es wird via TaskScheduler mit Administrator-Berechtigung gestartet und startet erfolgreich das Script s2.ps1 mit Administrator-Berechtigung, ohne dass irgendwer mit Administrator-Berechtigung am System angemeldet ist.

    • Als Antwort markiert dahlgrimm Donnerstag, 15. Dezember 2016 17:12
    Donnerstag, 15. Dezember 2016 17:12

Alle Antworten

  • >       <UserId>WIN-2I9GJLF4IE4\Administrator</UserId>
    >       <LogonType>Password</LogonType>
    >       <RunLevel>LeastPrivilege</RunLevel>
     Das hat mit Powershell eher nichts zu tun. Du startest das als "Administrator", aber nicht elevated (-> "LeastPrivilege"). Damit hat Administrator vmtl. keinen Zugriff auf c:\users\dede\s1.log und Add-Content wirft einen terminating error.
     
    Du sagst zwar, daß s1.ps1 noch gestartet wird - aber stimmt das wirklich? Oder wird vielleicht nur powershell.exe gestartet - prüf mal den Task Returncode im Eventlog :-)
     
    Donnerstag, 15. Dezember 2016 11:32
  • > Das hat mit Powershell eher nichts zu tun.
    Völlig richtig. Ich bin doch hier auch im "Windows Server"-Forum!?   ;-)

    > Du startest das als "Administrator", aber nicht elevated (-> "LeastPrivilege").
    Ich hatte spaßeshalber auch schon mal "mit höchsten Berechtigungen ausführen" markiert. Hat nix geändert.

    > Du sagst zwar, daß s1.ps1 noch gestartet wird - aber stimmt das wirklich?
    Das stimmt sicher. Sonst würden ja keine Einträge in s1.log geschrieben werden.

    > prüf mal den Task Returncode im Eventlog :-)
    Ich bekomme alle zwei Minuten Einträge wie diese:
    Informationen im Anwendungs-Log:

    Es wurde festgestellt, dass Ihre Registrierungsdatei noch von anderen 
    Anwendungen oder Diensten verwendet wird. Die Datei wird nun entladen.
    Die Anwendungen oder Dienste, die Ihre Registrierungsdatei anhalten,
    funktionieren anschließend u. U. nicht mehr ordnungsgemäß.
    Kein Benutzereingriff erforderlich.

    Fehler unter Administrative Ereignisse:

    Einstellungen: Schlüssel aus der Registrierung konnte nicht gelesen werden. 
    (Ausnahme von HRESULT: 0x80040150 (REGDB_E_READREGDB)

    Im Verlauf vom TaskScheduler kommen pro Lauf immer die selben sechs Meldungen. Egal ob s2.ps1 von s1.ps1 erfolgreich gestartet wurde, oder nicht.

    Das bizarre an der Sache ist: sobald ich (als Benutzer "dede") irgendwas als Administrator öffne (wie etwa die Computerverwaltung), laufen schlagartig beide Scripte.
    Nur nützt mir das natürlich rein gar nix. Da soll sich nach einem Reboot ja nicht immer erst mal jemand anmelden müssen, damit der Server tut, was er tun soll.

    Donnerstag, 15. Dezember 2016 12:27
  • Ich habe jetzt einen Workaround gefunden. Der Weg ist zwar total krank, aber bisher der einzig funktionierende....und "Lösung" will ich das mal nicht nennen.

    In s1.ps1 wird jetzt ein weiterer ScheduledTask erzeugt, dessen Ausführungszeit zwei Sekunden in der Zukunft liegt und der s2.ps1 mit Administrator-Berechtigungen startet.

    $UserPasswdFile="c:\xxxxxxx\xml\UserPasswd.xml"
    
    function SetupScheduledTask($UserPasswd) {
        $ts=New-TimeSpan -Seconds 2
        $execTime=(Get-Date) + $ts
        $job="-NoLogo -WindowStyle hidden -Command " + "C:\Users\dede\s2.ps1"
        $action=New-ScheduledTaskAction -Execute "powershell" -Argument $job
        $trigger=New-ScheduledTaskTrigger -Once -At $execTime
        $task=New-ScheduledTask -Action $action -Trigger $trigger
        $tn="delete_me_" + (get-date -format yyyy.MM.dd_HH-mm-ss.fff)
        $rc=Register-ScheduledTask -TaskName $tn -InputObject $task -User "$env:USERDOMAIN\$env:USERNAME" -Password $UserPasswd
    }
    
    ##############################################################
    # Liefert $True, wenn "$userCred" eine gültige Benutzerkennung
    # mit dem richtigen Passwort enthält.
    function checkCredentials($userCred) {
        Add-Type -assemblyname system.DirectoryServices.accountmanagement
        $DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine)
        return($DS.ValidateCredentials($userCred.Username, $userCred.GetNetworkCredential().Password))
    }
    
    ##############################################################
    # Liefert das Passwort aus der Datei "$UserPasswdFile" als
    # Klartext.
    function Load-UserLogonData  {
        try {
            $credentials=Import-Clixml $UserPasswdFile
        }
        catch {
            ("Datei nicht gefunden oder Passwort nicht entschlüsselbar für {0}!" -f $UserPasswdFile)
            exit 1
        }
        if(checkCredentials $credentials) {
            return($credentials.GetNetworkCredential().Password)
        } else {
            ("Die angegebene Benutzerkennung ist ungültig oder das Passwort ist falsch in {0}!" -f $UserPasswdFile)
            exit 1
        }
    }
    
    
    LogWrite([Environment]::UserName)
    #Start-Process -FilePath "powershell.exe" -ArgumentList "C:\Users\dede\s2.ps1"
    
    $UserPasswd=Load-UserLogonData
    SetupScheduledTask $UserPasswd
    

    Echt "durch die Brust ins Auge". Außerdem brauche ich jetzt noch einen Aufräum-Job, der die ganzen "delete_me_....."-Jobs wieder entfernt.

    Aber vielleicht hat ja doch noch jemand hier eine richtige Lösung für mich.....

    Donnerstag, 15. Dezember 2016 15:23
  • > Völlig richtig. Ich bin doch hier auch im "Windows Server"-Forum!?   ;-)
     
    Ojeh ich werd alt... Hab mich voll verguckt, als ich .ps1 gesehen habe :-)
     
    >> Du sagst zwar, daß s1.ps1 noch gestartet wird - aber stimmt das wirklich?
    > Das stimmt sicher. Sonst würden ja keine Einträge in s1.log geschrieben werden.
     
    Hab noch mal darüber nachgedacht. Start-Process erzeugt keinen Child-Prozess, sondern einen neuen. Ich erinnere mich dunkel, daß das aus dem Taskplaner nicht geht. Logging könnte helfen:
     
    Try{
     $NewProc = Start-Process [...]
     LogWrite( $( $NewProc.ToString() ) )
    }
    Catch {
      LogWrite( $( $_.Exception.Message ) )
    }
     
    Müßte funktionieren... :-)
     
    Donnerstag, 15. Dezember 2016 15:58
  • Leider nicht.

    Was bewirkt das erste $-Zeichen bei deinen LogWrite-Statements?

    Nur wenn ich dem Start-Process auch einen -PassThru übergebe, kommt überhaupt ein Rückgabewert. Also etwa so:

    LogWrite([Environment]::UserName)
    try {
        $NewProc=Start-Process -FilePath "powershell.exe" -ArgumentList "C:\Users\dede\s2.ps1" -PassThru
        LogWrite("gut")
        LogWrite($($NewProc.ToString()))
    } catch {
        LogWrite("schlecht")
        LogWrite($($_.Exception.Message))
    }

    Liefert in s1.log:

    2016.12.15 17:32:01 Administrator
    2016.12.15 17:32:01 gut
    2016.12.15 17:32:01 System.Diagnostics.Process (powershell)

    ...und s2.log wird nicht geschrieben.

    Öffne ich danach eine Powershell-Konsole als Administrator (nur öffnen, sonst nix), landen beim nächsten 2-Minuten-Event nochmal die selben Zeilen in s1.log - aber auch eine Zeile in s2.log:

    2016.12.15 17:38:01 Administrator


    Was soll mir das jetzt sagen?

    Nachtrag: nehme ich den -PassThru raus, kommt in s1.log das hier hinzu:

    2016.12.15 17:52:01 Administrator
    2016.12.15 17:52:01 gut
    2016.12.15 17:52:01 schlecht
    2016.12.15 17:52:01 Es ist nicht möglich, eine Methode für einen Ausdruck aufzurufen, der den NULL hat.
    

    Und ersetze ich -PassThru durch -Wait, kommt sonderbarerweise nochmal das hier:

    2016.12.15 17:58:06 Administrator
    2016.12.15 17:58:07 gut
    2016.12.15 17:58:07 schlecht
    2016.12.15 17:58:07 Es ist nicht möglich, eine Methode für einen Ausdruck aufzurufen, der den NULL hat.
    

    ABER in s2.log kommt das lang ersehnte:

    2016.12.15 17:58:06 Administrator
    Somit könnte man den -Wait als Lösung ansehen.

    Vielen Dank !

    • Bearbeitet dahlgrimm Donnerstag, 15. Dezember 2016 17:02 Nachtrag zugefügt
    Donnerstag, 15. Dezember 2016 16:42
  • Folgende Version von s1.ps1 funktioniert:

    Function LogWrite($LogData)  {
        $LogMsg=(Get-Date -Format "yyyy.MM.dd HH:mm:ss") + " " + $LogData
        Add-content "c:\Users\dede\s1.log" -Value $LogMsg
    }
    
    LogWrite([Environment]::UserName)
    Start-Process -FilePath "powershell.exe" -ArgumentList "C:\Users\dede\s2.ps1" -Wait
    
    Will heißen: es wird via TaskScheduler mit Administrator-Berechtigung gestartet und startet erfolgreich das Script s2.ps1 mit Administrator-Berechtigung, ohne dass irgendwer mit Administrator-Berechtigung am System angemeldet ist.

    • Als Antwort markiert dahlgrimm Donnerstag, 15. Dezember 2016 17:12
    Donnerstag, 15. Dezember 2016 17:12
  • > Start-Process -FilePath "powershell.exe" -ArgumentList "C:\Users\dede\s2.ps1" -Wait
     
    Das hatte ich ja oben schon geschrieben: Dunkle Erinnerung, daß man aus dem Task Scheduler keine neuen Prozesse starten kann, sondern nur Childs.
     
    Freitag, 16. Dezember 2016 08:59
  • > Was bewirkt das erste $-Zeichen bei deinen LogWrite-Statements?
     
    Scope, Scope :-)
     
    $_.Exception.Message ist eine Property von $_. Und das $() außen rum stellt sicher, daß es auch als Property genommen wird und nicht als Folge von $_ und .Exception.Message.
     
    > Nur wenn ich dem Start-Process auch einen -PassThru übergebe, kommt überhaupt ein Rückgabewert. Also etwa so:
     
    Ja, stimmt - per Default liefert Start-Process nichts zurück. Hatte ich nicht dran gedacht :)
     
    > *Nachtrag:* nehme ich den -PassThru raus, kommt in s1.log das hier hinzu:
    >
    > 2016.12.15 17:52:01 Administrator
    > 2016.12.15 17:52:01 gut
    > 2016.12.15 17:52:01 schlecht
    > 2016.12.15 17:52:01 Es ist nicht möglich, eine Methode für einen Ausdruck aufzurufen, der den NULL hat.
     
    Ja, weil jetzt $NewProc leer ist. Und "gut" und "schlecht" steht drin, weil jetzt erst der letzte LogWrite im Try{}-Block eine Exception wirft.
     
     
    Freitag, 16. Dezember 2016 09:02
  • > aus dem Task Scheduler keine neuen Prozesse starten kann, sondern nur Childs.

    Dazu passt aber nicht ganz, dass ich ja sehr wohl [vermeintlich] eigenständige Prozesse starten kann (also ohne den -Wait), wenn ich z.B. eine Powershell-Konsole als Administrator offen habe. Ärgerlicherweise habe ich diesen Umstand [deswegen] aber erst erkannt, als ich schon diverse Scripte erstellt hatte, die darauf basieren, dass sie über den TaskScheduler mit Administrator-Berechtigung gestartet werden.

    Und leider habe ich feststellen müssen, dass ich den SubScript-Start mit -Wait nicht in allen Fällen benutzen kann. Einige meiner auf diese Weise gestarteten Scripte können potentiell deutlich länger laufen, als das Wiederholungs-Intervall der ScheduledTask. Da brauche ich zwangsläufig eigenständige Prozesse.
    Und daher habe ich mir gerade zwei Funktionen ScheduleForSingleRun() und RemoveFinishedScheduledTask() gebaut, mit denen ich meinen oben skizzierten "kranken Workaround" implementiere.
    Das ist dann wohl "the Windows way".

    Freitag, 16. Dezember 2016 10:37