AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

SetSystemTime unter Windows 7

Ein Thema von RonnyBausA · begonnen am 29. Nov 2011 · letzter Beitrag vom 9. Dez 2011
Antwort Antwort
RonnyBausA

Registriert seit: 29. Nov 2011
10 Beiträge
 
Delphi XE2 Professional
 
#1

SetSystemTime unter Windows 7

  Alt 29. Nov 2011, 16:03
Hallo zusammen,
jetzt bitte nicht gleich stöhnen, dass diese Frage zum x-tausendsten Mal kommt. Ich programmiere übrigens mit Delphi 5 Pro.

Ich möchte mal kurz meine Problematik erläutern:
Ich weiß, dass ich die Systemzeit nur mit Administratorrechten ändern kann und das ist auch gut so. Erst mal zum Gesamtsystem. Es geht um ein digitales Steuerungssystem welches permanent über eine serielle Schnittstelle mit einem (meist Windows 7) PC verbunden ist. Auf diesem PC läuft eine kleine Anwendung in der Taskbar, welche die Kommunikation über die serielle Schnittstelle macht. Diese Anwendung kommuniziert mit mehreren anderen lokalen Anwendungen per Windows Messages. Das sind z.B. Anwendungen für Protokollierung oder Konfiguration dieses digitalen Systems. Alles läuft soweit unter einem normalen Benutzeraccount ohne Adminrechte wie es sich gehört. Der jeweils verwendete PC ist meistens alleine und ohne Netzwerkverbindung nur für dieses digitale System da und läuft 24h/7Tage. Die PC Anwendungen werden alle beim Windows Start per Autostart geladen für den Fall, das es trotz USV mal einen Aussetzer gibt. Das heißt auch, dass vor diesem PC nur in seltenen Fällen mal ein Benutzer sitzt.

Nun zu meinem Problem. Das digitale System verfügt über eine eigene DCF-Uhr. Diese Uhrzeitinformation gibt sie auch per serieller Schnittstelle an die Anwendung in der Taskbar. Nun wäre es schön, wenn diese Anwendung eben auch die PC-Zeit entsprechend setzen könnte, damit die Zeiten des digitalen Systems und des PCs synchron sind, gerade auch wegen der Fehlerprotokollierung. Da die Anwendung unter einem normalen Benutzeraccount läuft hat sie nicht das Recht die PC-Zeit zu ändern. Eine kleine Zusatzanwendung mit Adminrechten zu starten fällt denke ich ebenfalls aus, da dann ja die UAC nach Erlaubnis fragen würde. Da aber kein Benutzer vor dem PC sitzt wird dies scheitern. Ich hab auch schon mal testweise die Anwendung in der Taskbar als Administrator laufen lassen. Dann kann sie zwar die Uhrzeit ändern (wenn ich per Token das entsprechende Privileg anfordere), aber kann dann nicht mehr per Windows Messages mit den anderen Anwendungen kommunizieren, da diese unter dem normalen Benutzeraccount laufen.

Nach vielem Googlen tauchte öfter die Möglichkeit mit einem Windows Dienst auf. Dieser könnte ja mit dem lokalen Systemkonto theoretisch in der Lage sein, die Uhrzeit zu ändern. Nur wie sage ich dann z.B. dem Dienst "Hey, änder mal die Uhrzeit auf xxx"? Zumal ja die Anwendung und der Dienst unter verschiedenen Benutzerkonten arbeiten.

Hat irgendjemand eine gute und einfache Idee wie man das realisieren könnte? Es muss auch nicht unbedingt über einen Dienst gehen. Wenn jemand eine bessere Idee hat, nur her damit. Wie gesagt darf zu diesem Zeitpunkt keine Windows Sicherheitsabfrage oder ähnliches kommen, da kein Benutzer vorm PC sitzt. Andererseits möchte ich auch Windows-konform bleiben und nicht alle Schutzmechanismen aushebeln müssen.

Bin gespannt auf Eure Vorschläge. Bei komplizierteren Vorschlägen wäre ich auch dankbar für ein paar Codeschnipsel, da ich bisher eben nur die "normalen" Sachen programmiert habe.
Ronny B.
  Mit Zitat antworten Zitat
Benutzerbild von Bernhard Geyer
Bernhard Geyer

Registriert seit: 13. Aug 2002
17.196 Beiträge
 
Delphi 10.4 Sydney
 
#2

AW: SetSystemTime unter Windows 7

  Alt 29. Nov 2011, 16:09
Du hast schon das Schlagwort gebracht: Windows Dienst.

Dieser frägt auch deine DCF-Uhr ab so das du auch keine Kommunikation zwischen "normaler" App und Dienste App (welche z.B. über Named Pipes gehen würde) nötig wäre.
Windows Vista - Eine neue Erfahrung in Fehlern.
  Mit Zitat antworten Zitat
RonnyBausA

Registriert seit: 29. Nov 2011
10 Beiträge
 
Delphi XE2 Professional
 
#3

AW: SetSystemTime unter Windows 7

  Alt 29. Nov 2011, 16:17
Vielen Dank für die schnelle Antwort.

...Dieser frägt auch deine DCF-Uhr ab...
Die DCF-Uhr hängt nicht am PC sondern an dem externen digitalen Steuerungssystem. Dieses übermittelt in regelmäßigen Abständen die Uhrzeitinformation per serieller Schnittstelle an meine kleine Taskbar-Anwendung. Die DCF-Uhr muss auch extern bleiben, da das digitale Steuerungssystem aus einem komplexen Verbund mehrerer Systeme besteht.

Ich muss also die Daten an den Dienst übergeben, damit der die Uhrzeit setzen kann.

viele Grüße
Ronny B.

Edit: Ich hab auch noch nie mit Pipes gearbeitet, aber funktioniert das den auch von einer Anwendung mit normalem Benutzeraccount zu einem Dienst mit lokalem Systemaccount?

Geändert von RonnyBausA (29. Nov 2011 um 16:19 Uhr) Grund: Text angefügt
  Mit Zitat antworten Zitat
Benutzerbild von Bernhard Geyer
Bernhard Geyer

Registriert seit: 13. Aug 2002
17.196 Beiträge
 
Delphi 10.4 Sydney
 
#4

AW: SetSystemTime unter Windows 7

  Alt 29. Nov 2011, 16:26
Die DCF-Uhr hängt nicht am PC sondern an dem externen digitalen Steuerungssystem. Dieses übermittelt in regelmäßigen Abständen die Uhrzeitinformation per serieller Schnittstelle an meine kleine Taskbar-Anwendung. Die DCF-Uhr muss auch extern bleiben, da das digitale Steuerungssystem aus einem komplexen Verbund mehrerer Systeme besteht.
Auch der Dienst kann diese externen Info per Serielle Schnittstelle abfragen.
Windows Vista - Eine neue Erfahrung in Fehlern.
  Mit Zitat antworten Zitat
RonnyBausA

Registriert seit: 29. Nov 2011
10 Beiträge
 
Delphi XE2 Professional
 
#5

AW: SetSystemTime unter Windows 7

  Alt 29. Nov 2011, 16:37
Aber der Dienst und meine Taskbar Anwendung können ja nicht gleichzeitig auf ein und den selben COM-Port zugreifen. Und die Taskbar-Anwendung und deren benutzter COM-Port kann ich nicht schließen, da auf der seriellen Schnittstelle ein permanenter Datenverkehr mit dem digitalen System läuft. Die Uhrzeitinformation ist nicht das einzige, was über die serielle Schnittstelle kommt. Der Rest wird halt von den anderen Anwendungen verarbeitet. Die Taskbar-Anwendung händelt sozusagen das Datenprotokoll auf der seriellen Schnittstelle. Jedes fertige Datenpaket wird dann von dieser Taskbar-Anwendung aufbereitet und an die entsprechende und dafür zuständige Anwendung per Windows Message verteilt. Eine Unterbrechung des Datenverkehrs würde auch zu einer Fehlermeldung des digitalen Systems führen.

Viele Grüße
Ronny B.
  Mit Zitat antworten Zitat
Benutzerbild von Bernhard Geyer
Bernhard Geyer

Registriert seit: 13. Aug 2002
17.196 Beiträge
 
Delphi 10.4 Sydney
 
#6

AW: SetSystemTime unter Windows 7

  Alt 29. Nov 2011, 16:39
... Die Uhrzeitinformation ist nicht das einzige, was über die serielle Schnittstelle kommt. Der Rest wird halt von den anderen Anwendungen verarbeitet. ...
Ok. Dann brauchst du eine sonstige Interprozesskommunikation wie Named Pipes oder simple ein TCP/IP-Port über den du das schickst.
Wenn du hier im Forum nach "Named Pipe Dienst" suchst solltest du einiges an Beispiel(code) finden
Windows Vista - Eine neue Erfahrung in Fehlern.
  Mit Zitat antworten Zitat
RonnyBausA

Registriert seit: 29. Nov 2011
10 Beiträge
 
Delphi XE2 Professional
 
#7

AW: SetSystemTime unter Windows 7

  Alt 29. Nov 2011, 16:44
Danke erst einmal für die super schnelle Hilfe, das werde ich machen. Morgen bin ich nicht da, aber danach schaue ich mir das mal an und wenn noch fragen sind poste ich das.

Viele Grüße
Ronny B.
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe
Online

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.453 Beiträge
 
Delphi 12 Athens
 
#8

AW: SetSystemTime unter Windows 7

  Alt 29. Nov 2011, 17:39
Nur mal so ins Blaue gedacht: Du könntest deine Anwendung als NTP-Server implementieren und die Windows-eigenen Internetzeit-Einstellungen auf den Localhost verbiegen.
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
Benutzerbild von kuba
kuba

Registriert seit: 26. Mai 2006
Ort: Arnsberg
588 Beiträge
 
Delphi 11 Alexandria
 
#9

AW: SetSystemTime unter Windows 7

  Alt 29. Nov 2011, 19:23
Hallo,

den Datenaustausch habe ich bisher immer über Registry Einträge realisiert. Das Problem dabei ist, dass die Kommunikation über HKEY_CURRENT_USER erfolgen muss. Geht aber auch, ein Systemdienst kann Daten aus HKEY_CURRENT_USER lesen. Dazu muss die SID des Benutzers bekannt sein. Von einer Kommunikation über INI-Datei rate ich ab weil, je nach Intervall, zu viele Festplattenzugriffe das System ausbremsen könnten.

Beispiel:
Delphi-Quellcode:
procedure WriteUserRegistry;
var
  SID : PSID;
  strSID : PAnsiChar;
  s : String;
  err : DWORD;
  Registry : tRegistry;
begin
   err := GetAccountSid('', 'Benutzername', SID);
    if err = 0 then
    begin
     if ConvertSidToStringSid(SID, strSID) then
       s := strSID
     else
       s := SysErrorMessage(err);
    end
    else
     s := SysErrorMessage(err);

    Registry:=Tregistry.Create;
    Registry.Rootkey := HKEY_USERS;

      try
      Registry.OPENKEY(s + '\Software\MeineZeit', true);
      Registry.WriteString('Zeit','10:00:59');
      finally
      Registry.CloseKey;
      end;
end;
Lesen geht natürlich auch. Hab ich was vergessen ? Ach ja, dafür gibt es die Suchfunktion

kuba
Stefan Kubatzki
E=mc2
  Mit Zitat antworten Zitat
RonnyBausA

Registriert seit: 29. Nov 2011
10 Beiträge
 
Delphi XE2 Professional
 
#10

AW: SetSystemTime unter Windows 7

  Alt 9. Dez 2011, 14:51
So, ich wollte jetzt nur mal eine Rückmeldung geben.

Danke an Euch für die Tipps.
@Uwe Raabe: Das mit dem NTP Server ist auch ein interessanter Gedanke. Bei Googlen hab ich allerdings immer nur Beispiele zu NTP Clients gefunden.
@kuba: Das mit der Registry ist sicher ein einfacher Weg. Man muss nur sicherstellen, dass der Lesende nicht irgendeine alte Zeit ausliest.

Ich hab das Ganze jetzt über einen Dienst und named Pipes gelöst (Dank an Bernhard Geyer). Um mir das Leben leicht zu machen und weil ich nicht so viel Zeit hatte mich in die Materie der Named Pipes einzuarbeiten hab ich die pipes.pas von Russel Jordan genommen. Gefunden hab ich die z.B. hier. Das hab ich dann als Komponente sowohl im Dienst als auch in meiner Taskbar-Applikation verwendet. Hat sogar bei meinem betagten Delhi 5 Pro auf Anhieb funktioniert. Der Vorteil war außerdem, dass ich dann in meinem Dienst ein OnMessage-Event nutzen kann, so dass ich gar nicht selber dauernd in einer Schleife auf vorhandene Daten prüfen muss.

Delphi-Quellcode:
procedure TMyTimeSettingService.PipeServer1PipeMessage(Sender: TObject;
  Pipe: Cardinal; Stream: TStream);
var
  TimeVar: TSystemTime;
  Daten: Array[0..1024] of Byte;
  Count: Integer;
begin
  for Count := 0 to 16 do Daten[Count] := 0;
  Count := Stream.Size;
  Stream.Read(Daten[0],Count);
  if (Daten[0] = $59) then // Uhrzeitdaten beginnen bei mir mit $59
  begin
    FillChar(TimeVar, SizeOf(TimeVar), 0);
    TimeVar.wYear := (Daten[1] *256) + Daten[2];
    TimeVar.wMonth := (Daten[3] *256) + Daten[4];
    TimeVar.wDayOfWeek := (Daten[5] *256) + Daten[6];
    TimeVar.wDay := (Daten[7] *256) + Daten[8];
    TimeVar.wHour := (Daten[9] *256) + Daten[10];
    TimeVar.wMinute := (Daten[11] *256) + Daten[12];
    TimeVar.wSecond := (Daten[13] *256) + Daten[14];
    TimeVar.wMilliseconds := (Daten[15] *256) + Daten[16];
    Daten[0] := $59; // Antwort an Sender
    Daten[1] := 1;
    if SetPCSystemTime(TimeVar) then
      Daten[2] := 1 // Status OK
    else
      Daten[2] := 0; // Status False
    PipeServer1.Write(Pipe,Daten,3); // 3 Bytes zurücksenden
  end;
end;
Ich habe für den Stream den Typ Byte gewählt, weil meine Daten eh schon als Bytes vorliegen. Dabei hatte ich zuerst ein merkwürdiges Phänomen und zwar hatte ich zuerst als Typ Word genommen. Im OnMessage Ereignis hatte ich dann zwar die richtige Anzahl (Count). Wenn ich dann aber "Count" Anzahl Daten aus dem Stream gelesen habe, hatte ich danach nur die Hälfte der Daten, da jedes Lesen immer nur ein Byte geholt hatte. Na egal, hab's auf Typ Byte geändert und nun gehts.

Zum endgültigen Setzen der Uhrzeit verwende ich dann folgenden Code:
Delphi-Quellcode:
function SetPCSystemTime(dSysTime: TSystemTime): Boolean;
const
  SE_SYSTEMTIME_NAME = 'SeSystemtimePrivilege';
var
  hToken: THandle;
  ReturnLength: DWORD;
  tkp, PrevTokenPriv: TTokenPrivileges;
  luid: TLargeInteger;
begin
  Result := False;
  if (Win32Platform = VER_PLATFORM_WIN32_NT) then
  begin
    if OpenProcessToken(GetCurrentProcess,
      TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken) then
    begin
      try
        if not LookupPrivilegeValue(nil, SE_SYSTEMTIME_NAME, luid) then Exit;
        tkp.PrivilegeCount := 1;
        tkp.Privileges[0].luid := luid;
        tkp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
        if not AdjustTokenPrivileges(hToken, False, tkp, SizeOf(TTOKENPRIVILEGES),
          PrevTokenPriv, ReturnLength) then
          Exit;
        if (GetLastError <> ERROR_SUCCESS) then
        begin
          LogMessage('Das angeforderte Recht zum Setzen der Uhrzeit wurde nicht gewährt. Meldung vom System: '+ SysErrorMessage(GetLastError) + '. ' +
                     'Prüfen Sie, ob der Dienst unter dem lokalen System-Konto ausgeführt wird.', EVENTLOG_ERROR_TYPE, 0, 4);
          Exit;
        end;
      finally
        CloseHandle(hToken);
      end;
    end;
  end;
  Result := Windows.SetLocalTime(dSysTime);
end;
Wie man sieht benutze ich die LogMessage Funktion, um bei Nichterfolg dieses zu Protokollieren. Dazu fand ich das Tutorial von Finn Tolderlund sehr hilfreich. Für diejenigen die auf der Suche nach dem Message Compiler (mc.exe) sind: Googled mal nach GRMSDK_EN_DVD.iso. Zu finden unter Microsoft Windows SDK for Windows 7 and .NET Framework 4 (ISO). Wenn man die ISO Datei öffnet/mounted findet man unter Setup\WinSDKWin32Tools eine Installation mit der unter andrem der mc.exe installiert wird. Man muss dann nicht gleich das ganze Visual Studio installieren, nur um den Message Compiler zu bekommen.

Und der vollständigkeit halber: Installiert wird der Dienst bei mir zusammen mit der Taskbar-Anwendung mit Inno Setup. Dazu registriert bzw. deregistriert sich der Dienst selbst. Das Inno Setup Script dazu sieht so aus (ich hoffe der Code-Tag zeigt das halbwegs richtig an):
Code:
[Run]
Filename: {app}\MyTimeService.exe; Parameters: " /install /silent"; WorkingDir: {app}
[UninstallRun]
Filename: {app}\MyTimeService.exe; Parameters: " /uninstall /silent"; WorkingDir: {app}
Da man bei einem Update den Dienst ja erst stoppen muss hab ich im Inno Script in der Code-Sektion das Beispiel von Silvio Iaccarino eingefügt.
Im Inno Script habe ich dann Pascal-Code für CurStepChanged und CurUninstallStepChanged eingefügt:
Code:
procedure CurStepChanged(CurrentStep: TSetupStep);
var
  I: Integer;
  Flag: Boolean;
begin
   if CurrentStep = ssInstall then
   begin
      if IsServiceInstalled('{#ServiceName}') = true then
      begin
         if IsServiceRunning('{#ServiceName}') = true then
         begin
            Flag := false;
            StopService('{#ServiceName}');
            // after stopping a service you should wait some seconds before removing
            // otherwise removing can fail
            ProgressPage.SetText('Stoppe den laufenden Dienst...', '');
            ProgressPage.SetProgress(0, 0);
            ProgressPage.Show;
            try
               for I := 0 to 100 do
               begin
                  ProgressPage.SetProgress(I, 100);
                  Sleep(100);
                  if IsServiceRunning('{#ServiceName}') = false then
                  begin
                     Flag := True;
                     Sleep(100);
                     Break;
                  end;
               end;
               if not Flag then
                  MsgBox('Der Dienst {#ServiceName} konnte nicht gestoppt werden!',mbInformation, MB_OK);
            finally
               ProgressPage.Hide;
            end;
         end;
         RemoveService('{#ServiceName}');
      end;
   end;
   if CurrentStep = ssPostInstall then
   begin
      if IsServiceInstalled('{#ServiceName}') = true then
      begin
         Flag := false;
         StartService('{#ServiceName}');
         ProgressPage.SetText('Starte den Dienst...', '');
         ProgressPage.SetProgress(0, 0);
         ProgressPage.Show;
         try
            for I := 0 to 100 do
            begin
               ProgressPage.SetProgress(I, 100);
               Sleep(100);
               if IsServiceRunning('{#ServiceName}') = true then
               begin
                  Flag := True;
                  Sleep(100);
                  Break;
               end;
            end;
            if not Flag then
               MsgBox('Der Dienst {#ServiceName} konnte nicht gestartet werden!',mbInformation, MB_OK);
         finally
            ProgressPage.Hide;
         end;
      end;
   end;
end;

procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
  I: Integer;
  Flag: Boolean;
begin
   if CurUninstallStep = usUninstall then
   begin
      if IsServiceInstalled('{#ServiceName}') = true then
      begin
         if IsServiceRunning('{#ServiceName}') = true then
         begin
            Flag := false;
            StopService('{#ServiceName}');
            // after stopping a service you should wait some seconds before removing
            // otherwise removing can fail
            for I := 0 to 100 do
            begin
               Sleep(100);
               if IsServiceRunning('{#ServiceName}') = false then
               begin
                  Flag := True;
                  Sleep(100);
                  Break;
               end;
            end;
            if not Flag then
               MsgBox('Der Dienst {#ServiceName} konnte nicht gestoppt werden!',mbInformation, MB_OK);
         end;
      end;
   end;
end;
{#ServiceName} ist bei mir im Script als Konstante definiert und ist der Name (nicht der DisplayName) des Dienstes.

In meiner Systray-App kann ich jetzt sehr schön auch prüfen ob der Dienst läuft. Dazu hab ich das Beispiel hier aus dem Forum von CalganX genommen.

Soweit alles gut. Ich hoffe meine Ausführungen zum Message Compiler und Inno Setup waren nicht zu sehr OT, aber ich denke das andere dieses vielleicht auch hilfreich finden.

Eine kleine Frage hab ich dann aber doch noch: In meiner Systray-Anwendung möchte ich dem Nutzer die Möglichkeit geben über einen Button den Dienst auch mal zu stoppen und wieder zu starten. Könnte ja vielleicht bei Fehlereingrenzung hilfreich sein und möchte dann ungern den Benutzer per Telefon erst zu den Diensten lotsen müssen. Aber um Meine Dienst zu starten oder zu stoppen brauch ich ja Adminrechte, da der Dienst unter dem lokalen Systemkonto läuft. Also angenommen ich hab folgende Button-Click Methode:
Delphi-Quellcode:
procedure TSubForm.Button2Click(Sender: TObject);
begin
  Label1.Caption := 'Warte...';
  Application.ProcessMessages;
  if ServiceStop('','MyTimeSettingService') then
    Label1.Caption := 'OK'
  else
    Label1.Caption := 'Fehler';
  Label2.Caption := IntToStr(ServiceGetStatus('','MyTimeSettingService'));
end;
Ist es auf einfachem Weg möglich die Funktion ServiceStop mit Adminrechten auszuführen? Die Windows UAC-Abfrage ist dabei kein Problem und soll auch kommen, da zu diesem Zeitpunkt ja auch ein Benutzer vor dem PC sitzt.

Vielen Dank
Ronny
  Mit Zitat antworten Zitat
Antwort Antwort

 

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:13 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz