.net - Sådan startes screensaver fra systemtjenesten

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg har flere varianter til at starte pauseskærmen. Jeg er favorit


[DllImport("user32.dll", SetLastError = false)]
private static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

private void startScreensaver()
{
    UInt32 WM\_SYSCOMMAND = 0x112;
    IntPtr SC\_SCREENSAVE = new IntPtr(0xf140);
    IntPtr hWnd = GetDesktopWindow();
    SendMessage(hWnd, WM\_SYSCOMMAND, SC\_SCREENSAVE, new IntPtr(0));
}


Mit problem er, at jeg vil starte screensaveren ud af en systemtjeneste. Hvis jeg f.eks. Ønsker du at starte pauseskærmen, så snart sessionen er låst (bare for bevis på koncept), kunne jeg prøve


protected override void OnSessionChange(SessionChangeDescription changeDescription)
{
    base.OnSessionChange(changeDescription);
    if (changeDescription.Reason == SessionChangeReason.SessionLock)
        startScreensaver();
}


Dette virker ikke, og jeg tror, ​​at grunden er, at tjenesten er installeret med


ServiceProcessInstaller.Account = ServiceAccount.LocalSystem;


som ikke har adgang til brugerens session. Jeg kunne implementere et lille program, der kører i brugeresessionen, som udløses af tjenesten til at udløse screensaveren ... men det er ikke den dejlige måde.


Nogen forslag? Tak.


redigeret: Selvfølgelig er problemet relateret til GetDesktopWindow(); opkaldet, jeg ved stadig ikke, hvordan man løser det


Opdatering:


Ifølge Erics forslag, jeg gør nu iterate alle vinduesstationer (ved hjælp af OpenWindowStation), så for alle dem jeg gentager alle desktops (ved hjælp af EnumDesktops). Jeg åbner derefter desktopsne ved hjælp af OpenDesktop og gemmer håndtaget til skrivebordet. Min standard Windows installation leverer til følgende liste over windowStation: Desktop: dskHandle



  • WinSta0: Standard: 732

  • WinSta0: Afbryd: 760

  • WinSta0: Winlogon: 784

  • msswindowstation: mssrestricteddesk: 0



Jeg starter nu en ny tråd, hvor jeg


[DllImport("user32.dll", SetLastError = true)]
static extern bool SetThreadDesktop(IntPtr hDesktop);


og derefter påberåbe startScreensaver () -metoden ovenfor. IntPtr hWnd = GetDesktopWindow() returnerer rimelige resultater, men screensaveren er ikke startet. I


[DllImport("user32.dll")]
static extern IntPtr OpenDesktop(string lpszDesktop, uint dwFlags, bool fInherit, uint dwDesiredAccess);


Jeg bruger GENERIC\_ALL = 0x10000000 som dwDesiredAccess. Og som Farzin bemærkede, kontrollerede jeg



  Tillad tjeneste at interagere med skrivebordet



Jeg er ikke win32 eller pInvoke pro, så jeg er helt tabt nu. Kan sb forklare, hvordan alle ting fungerer sammen? Har sb et bedre forslag? Alt jeg vil gøre er at aktivere screensaveren fra en systemtjeneste.

Bedste reference


OKAY. Jeg formaterede denne indlæg med links i håb om at hjælpe, og da klikket på Post fik dette:


Ups! Dit svar kunne ikke indsendes, fordi:
• Vi beklager, men som en spamforebyggelsesmekanisme kan nye brugere kun sende maksimalt to hyperlinks. Tjen mere end 10 ry for at sende flere hyperlinks.



Så her går du, helt ulæselig udstationering ....


Gå her for disse meddelelser med hyperlinks:
http://social.msdn.microsoft.com/Forums/en-US/vcgeneral/thread/89485c95-61e5-46ea-84c7-5d8e03081c61[18]


Erik, Farzin Zaker og OP, må ikke bruge SERVICE\_INTERACTIVE\_PROCESS-flag eller heller ikke benytte Interactive Services-teknikker. Denne tilgang er blevet udfaset siden Windows Vista. [[Mere her]]:


Fra Microsofts egne ord:



   Vigtigt Tjenester kan ikke direkte interagere med en bruger som i Windows Vista. Derfor bør de teknikker, der er nævnt i afsnittet 'Brug af en interaktiv tjeneste', ikke anvendes i ny kode.



Så selvom nogen af ​​de nævnte fremgangsmåder ovenfor arbejde, ville de ikke være noget andet end et 'hack' og kan stoppe med at arbejde i en ny version eller endda en opdatering til Windows.


Din bedste indsats for at gøre det, du ønsker, er gennem det, du nævnte dig selv, ' Jeg kunne implementere et lille program, der kører i brugeresessionen, som udløses af tjenesten til at udløse screensaveren '.


Tro mig, jeg har brugt utallige timer på at forsøge at gøre, hvad du vil ( den forkerte måde ), og jeg svigtede. Her er hvordan Microsoft gør det selv i deres software og hvordan du skal gøre det:



  1. Opret i din systemtjeneste en global navngivet auto-reset-begivenhed, angiv sin tilstand til ikke-sginaled. Sørg for at justere sikkerhedsbeskrivelsen for denne begivenhed læses og synkroniseres med 'Alle'. Mere [[her]] og [[her]] og [[her]] om oprettelse af en sikkerhedsbeskrivelse. Dette trin er vigtigt, hvis du ikke vil håndtere ERROR\_ACCESS\_DENIED senere.

  2. Lav et lille Win32 GUI-program, der har et skjult vindue. Ved starten åbner den globale begivenhed skabt af tjenesten ovenfor. Hvis jeg skulle skrive det i C ++, så ser det sådan ud: OpenEvent (READ\_CONTROL | SYNCHRONIZE, FALSE, \_T ('Global \\ Whatever\_name\_you\_use'));
    Derefter opret en arbejdstråd, der simpelthen venter på, at denne begivenhed bliver signaleret ved hjælp af et af WaitFor * Objekt-API'erne fra [[synkroniseringsfunktionerne]]. Sørg selvfølgelig for at arbejdstrådens tråd håndterer situationen, når dette lille GUI-program lukker.

  3. Fra arbejdstagerens tråd køres følgende kode, når den globale navngivne automatisk nulstillingshændelse bliver signaleret. Send WM\_SYSCOMMAND-beskeden til sit eget vindue i hoved GUI-tråden med wParam=SC\_SCREENSAVE og lParam=0, eller gør det via et opkald til DefWindowProc () API fra hoved GUI-tråden. Dette bør starte den aktuelt indstillede pauseskærm til brugeren, hvor GUI-programmet kører.

  4. Hvis du vil starte en bestemt pauseskærm, kan du bare køre den ved hjælp af ShellExecute med parameteren/s fra dit GUI-program. (Selvfølgelig skal du gøre det fra arbejdstrådens tråd, når den globale navngivne auto-reset-hændelse signaliseres.) Alle screensavers er normalt placeret i mappen '\% WINDIR\% \ System32'. De har .scr udvidelsen.

  5. OK, nu hvordan man aktiverer det fra systemtjenesten.

  6. Når du skal køre din screensaver, skal du sikre dig, at dit lille GUI-program kører i en brugersession, der aktuelt er aktiv. Den del, der er aktiv , er vigtig. Der er to apporaches her. Først. Du kan gøre det hver gang en brugersession bliver aktiv (af cource ved at lukke en kopi af dette GUI-program til en session, der holder op med at være aktiv. Du kan lukke det ved at udstede en kommando via en global navngiven begivenhed. Og du kan spore brugeren sessionsændringer fra din systemtjeneste og ServiceHandlerEx () ved at indhente SERVICE\_CONTROL\_SESSIONCHANGE meddelelser.) Du kan også køre dette GUI-program lige, når du skal aktivere pauseskærmen og derefter lukke den øjeblikkeligt. Jeg overlader det til dig, hvilken fremgangsmåde du vælger. Hovedpunktet er, at du på en eller anden måde skal køre dit GUI-program i en aktiv brugersession og bruge globale navngivne begivenheder til at kommunikere med den. (Du kan også inkorporere nogen [[andre midler til IPC]]. I min bog er globale begivenheder enkleste at formidle en boolesk eller 'ja og nej' type kommando.) Jeg skal fortælle dig lige ud af flagermus, at starte en proces i en anden brugersession er den mest arbejdskraft intensive del her, er dårligt dokumenteret og er svært at fejle. I et nøddokument skal du bruge CreateProcessAsUser () API'en fra din systemtjeneste, men den hårde del er prepping for opkaldet til API'en. Desværre er der ingen klar -konsensus om, hvordan man kalder det og der 'sa [[en masse råd til rådighed på internettet]], der er noget anderledes. De trin, der fungerede for mig, er som følger:



    • Placer dit GUI-program til et almindeligt tilgængeligt sted (selv for de mindst privilegerede brugere). Da det er en del af systemtjenesten, kan du bruge '\% WINDIR\% \ System32' men sørg for for at fjerne det derfra, når det ikke længere er nødvendigt!

    • Få den aktuelle aktive session ved at ringe til WTSEnumerateSessions () og se en session med WTSActive-tilstanden.

    • WTSQueryUserToken () for at få det aktive brugersessionstoken

    • DuplicateTokenEx (, MAXIMUM\_ALLOWED, NULL, SecurityIdentification, TokenPrimary, u0026;);

    • Opret miljøstrengene med et opkald til CreateEnvironmentBlock ()

    • Indlæs brugerprofil ved at ringe LoadUserProfile (). Du kan indsamle al nødvendig information før med følgende API'er: NetUserGetInfo () for profilbanen og WTSQuerySessionInformation (WTS\_CURRENT\_SERVER\_HANDLE, WTSUserName, & ;, &) for at få et session brugernavn.

    • Og udgive denne bruger med et opkald til ImpersonateLoggedOnUser ()

    • Ring på nuværende tidspunkt CreateProcessAsUser () på placeringen af ​​dit GUI-program, hvor du placerede det. Lad mig gentage, at du skal køre den fra den placering, der er tilgængelig for brugeren, som du netop har imiteret! Den almindelige fejl her kører den fra en placering, der ligner dette: ' C: \ Users \ SomeUserName \ AppData \ Roaming '. Dette opkald kan se sådan ud:
      CreateProcessAsUser (hToken2, NULL, pNonConstOrStaticBufferWithPathToGUIProgram, NULL, NULL, FALSE, NORMAL\_PRIORITY\_CLASS | CREATE\_NEW\_CONSOLE | CREATE\_UNICODE\_ENVIRONMENT, pEnvironmentBlock, NULL, &pSTARTUPINFO, &pPROCESS\_INFORMATION);

    • Tilbagekald altid impersonation: RevertToSelf ();

    • WaitForInputIdle () for at sikre, at din GUI-proces startede og nåede meddelelsespumpen.

    • Ryd op ved at ringe til UnloadUserProfile (), DestroyEnvironmentBlock (), WTSFreeMemory (), CloseHandle () osv.

    • Nu kan du indstille din globale navngivne automatisk nulstil hændelse ved at kalde SetEvent () for at signalere din GUI-proces for at starte screensaver. Og du er færdig! Du kan også gerne aktivere en slags tilbagemeldinger fra et GUI-program for at sikre, at screensaveren faktisk er startet, men jeg vil overlade det til dig. Igen henvises til [[midlerne til IPC]] for måder at gøre det på.




Som en konklusion, lad mig sige, at ovenstående tilgang er blevet indsamlet gennem en utallige forumposteringer og ved at skaffe flere websøgninger op. Og ja, jeg forstår, hvor stor og omhyggelig denne tilgang er, men hej, det er hvad Windows er, det er det ikke :) Hvis du vil have enkelhed, skal du gå OS X eller iOS. Det er hvad jeg til sidst gjorde


Skål.


PS. Hvem i verden kom op med formateringsreglerne på dette forum? Det er den sværeste ting at skrive ind og få noget læsbart ....

Andre referencer 1


gå til dine tjenester, højreklik på tjenesten og i fanen LogOn indstilles elementet bellow til true:



  Tillad tjeneste at interagere med skrivebordet



hvis du vil gøre dette ved installation:


public WindowsServiceInstaller()
{
  // This call is required by the Designer.

  InitializeComponent();
  ServiceInstaller si = new ServiceInstaller();
  si.ServiceName = "WindowsService1"; 
  si.DisplayName = "WindowsService1";
  si.StartType = ServiceStartMode.Manual;
  this.Installers.Add(si);
  ServiceProcessInstaller spi = new ServiceProcessInstaller();
  spi.Account = System.ServiceProcess.ServiceAccount.LocalSystem; 
  spi.Password = null;
  spi.Username = null;
  this.Installers.Add(spi);

  // Here is where we set the bit on the value in the registry.

  // Grab the subkey to our service

  RegistryKey ckey = Registry.LocalMachine.OpenSubKey(
    @"SYSTEMCurrentControlSetServicesWindowsService1", true);
  // Good to always do error checking!

  if(ckey != null)
  {
    // Ok now lets make sure the "Type" value is there, 

    //and then do our bitwise operation on it.

    if(ckey.GetValue("Type") != null)
    {
      ckey.SetValue("Type", ((int)ckey.GetValue("Type") | 256));
    }
  }
}


Reference: http://www.codeproject.com/KB/install/cswindowsservicedesktop.aspx[19]

Andre referencer 2


Det kan virke, at tjenesten kan interagere med skrivebordet, når du installerer (Pass SERVICE\_INTERACTIVE\_PROCESS til CreateService). Ellers (der kan være problemer med adgang - jeg har ikke prøvet dette), du skal begynde med Window Station og Desktop funktioner. [20]


Det, du skal gøre, er at finde den indloggede brugere vinduestation (EnumWindowStations, OpenWindowStation), skrivebordet (EnumDesktops, OpenDesktop), opret en tråd og SetThreadDesktop, så brug endelig GetDesktopWindow.