Gegen Internetzensur
Stoppt die Vorratsdatenspeicherung! Jetzt klicken & handeln!Willst du auch bei der Aktion teilnehmen? Hier findest du alle relevanten Infos und Materialien:
  • Flickr Photos

    www.flickr.com
31. Jul
Don

C# Code Snippets: Vista UAC elevation

Seit einiger Zeit ärgere ich mich mit Vistas “User Account Control”-Berechtigungsmechanismus herum, insbesondere mit der Frage, ob es möglich ist, Berechtigungen für eine bestimmte Anfrage anzufordern. Grundsätzlich scheint der Tenor zu sein: Nein, geht nicht. Berechtigungen gibt es entweder für den gesamten Prozess – und zwar beim Start – oder gar nicht.

Eine Möglichkeit in den Genuss erhöhter Berechtigungen zu kommen ist das Manifest einer Anwendung. In Visual Studio 2008 lässt sich ein solches “Application Manifest File” recht locker zum Projekt hinzufügen (es wird dann auch in den Projekteinstellungen als Manifest angewählt”. Um z.B. immer mit vollen Berechtigungen zu fahren – d.h., den Benutzer bei jedem Programmstart einen Bestätigungsklick abzufordern – kann man folgendes Manifest vergeben:

  1. <trustinfo xmlns="urn:schemas-microsoft-com:asm.v2">
  2.     <security>
  3.       <requestedprivileges xmlns="urn:schemas-microsoft-com:asm.v3">
  4.         <requestedexecutionlevel level="requireAdministrator" uiAccess="false" />
  5.       </requestedprivileges>
  6.     </security>
  7.   </trustinfo>

Der Knackpunkt ist hier requireAdministrator: “Administratorrechte sind (immer) nötig”. Als absolutes Minimum unter Vista sollte IMHO immer asInvoker gesetzt sein, d.h.: “Starte das Programm mit den Rechten des aufrufenden Benutzers”. Egal wie: Nur anhand dieses Manifestes erkennt Vista, dass ein Programm sich mit den Gepflogenheiten der neuen Umgebung Vista auskennt und wendet Technologien wie die Ordnervirtualisierung nicht an. Das führt dazu, dass Zugriffe auf Order wie C:\Program Files nicht nach C:\users\...xyz...\AppData\Local\VirtualStore\Program Files virtualisiert werden, sondern einfach mit einer UnauthorizedAccessException fehlschlagen.

Mehr zu den Manifesten etwa hier auf CodeProject.

Um zu testen, ob eine Anwendung mit erhöhten Privilegien läuft oder um sie mit eben solchen (erneut) zu starten (und wie man solche niedlichen “Schild”-Buttons hinbekommt), zeigt dieser Artikel auf CodeProject.

Das ist die Zusammenfassung:

  1. static internal bool IsElevated()
  2. {
  3.         WindowsIdentity id = WindowsIdentity.GetCurrent();
  4.         WindowsPrincipal p = new WindowsPrincipal(id);
  5.         return p.IsInRole(WindowsBuiltInRole.Administrator);
  6. }

… testet, ob die Anwendung mit vollen Administratorrechten läuft (und folglich Zugriff auf alle Ressourcen hat, die nicht durch Codesicherheitsrichtlinien eingeschränkt sind) und …

  1. ProcessStartInfo startInfo = new ProcessStartInfo();
  2. startInfo.UseShellExecute = true;
  3. startInfo.WorkingDirectory = Environment.CurrentDirectory;
  4. startInfo.FileName = Assembly.GetExecutingAssembly().Location;
  5. startInfo.Verb = "runas";
  6. try
  7. {
  8.         Process p = Process.Start(startInfo);
  9.         // Erfolg; Diesen Prozess können wir nun beenden
  10.         // Application.Exit() o.ä.
  11. }
  12. catch (System.ComponentModel.Win32Exception ex)
  13. {
  14.         // Benutzer hat abgebrochen
  15. }

… startet den Prozess als Administrator neu (Rückfrage unter Vista) bzw. springt in den catch-Block, wenn der Benutzer die erhöhten Rechte verweigerte.

Weiterhin lässt sich sagen, dass ein Prozess, der mit erhöhten Privilegien läuft, diese auch an alle Prozesse weiterreicht, die von ihm gestartet werden. Würde man denselben Process.Start()-Code also aus einem “elevated”-Prozess heraus starten, bliebe die Nachfrage aus.

Wer will, kann mit dem folgenden Testprogramm ja mal etwas herumspielen – es ist nur eben nötig, ein entsprechendes Manifest zu setzen. Ich habe für diesen Zweck "asInvoker" gewählt, um prinzipiell im Benutzermodus zu starten, es sei denn, das Programm wurde explizit als Admin aufgerufen.

Zum Download: C# Vista Permission Test (ZIP)
Und zum Lesen:

  1. using System;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.Reflection;
  5. using System.Security.Principal;
  6. using System.Threading;
  7.  
  8. namespace PermissionTest
  9. {
  10. class Program
  11. {
  12.         static void Main(string[] args)
  13.         {
  14.                 // Vorbereiten
  15.                 Thread.CurrentThread.Name = "Vista Permission Test";
  16.                 Console.WriteLine("Vista Permission Test" + Environment.NewLine);
  17.                
  18.                 // Sind wir "elevated"?
  19.                 Console.WriteLine("Läuft mit erhöhten Privilegien: " + (IsAdmin() ? "ja" : "nein"));
  20.  
  21.                 // ProgramFiles
  22.                 Console.WriteLine(Environment.NewLine + "Teste Programme-Ordner …");
  23.                 TestDirectory(Environment.SpecialFolder.ProgramFiles);
  24.                 TestDirectory(Environment.SpecialFolder.CommonProgramFiles);
  25.  
  26.                 // Anwendungsdaten
  27.                 Console.WriteLine(Environment.NewLine + "Teste Anwendungsdaten-Ordner …");
  28.                 TestDirectory(Environment.SpecialFolder.ApplicationData);
  29.                 TestDirectory(Environment.SpecialFolder.CommonApplicationData);
  30.  
  31.                 // Level erhöhen
  32.                 if (!IsAdmin())
  33.                 {
  34.                         Console.Write(Environment.NewLine + "Elevation-Test durchführen? [Jn]");
  35.                         ConsoleKeyInfo key;
  36.                         do
  37.                         {
  38.                                 key = Console.ReadKey(true);
  39.                         } while (key.Key != ConsoleKey.J && key.Key != ConsoleKey.N && key.Key != ConsoleKey.Enter);
  40.                         Console.WriteLine();
  41.  
  42.                         // Test durchführen?
  43.                         if( key.Key == ConsoleKey.J || key.Key == ConsoleKey.Enter )
  44.                         {
  45.                                 Console.Write("Fordere erhöhtes Level an… ");
  46.                                
  47.                                 ProcessStartInfo startInfo = new ProcessStartInfo();
  48.                                 startInfo.UseShellExecute = true;
  49.                                 startInfo.WorkingDirectory = Environment.CurrentDirectory;
  50.                                 startInfo.FileName = Assembly.GetExecutingAssembly().Location;
  51.                                 startInfo.Verb = "runas";
  52.                                 try
  53.                                 {
  54.                                         Process p = Process.Start(startInfo);
  55.                                         Console.WriteLine("OK");
  56.                                         return;
  57.                                 }
  58.                                 catch (System.ComponentModel.Win32Exception ex)
  59.                                 {
  60.                                         if( ex.NativeErrorCode == 1223 )
  61.                                                 Console.WriteLine("Fehlschlag. (Abbruch durch Benutzer)");
  62.                                         else
  63.                                                 Console.WriteLine("Fehlschlag. (Fehler " + ex.NativeErrorCode + ")");
  64.                                 }
  65.  
  66.                         }
  67.                         else
  68.                         {
  69.                                 Console.WriteLine("Führe keinen elevation-Test durch.");
  70.                         }
  71.                 }
  72.  
  73.                 // Prozess erneut starten
  74.                 if( IsAdmin() )
  75.                 {
  76.                         Console.Write(Environment.NewLine + "Vererbungs-Test durchführen? [Jn]");
  77.                         ConsoleKeyInfo key;
  78.                         do
  79.                         {
  80.                                 key = Console.ReadKey(true);
  81.                         } while (key.Key != ConsoleKey.J && key.Key != ConsoleKey.N && key.Key != ConsoleKey.Enter);
  82.                         Console.WriteLine();
  83.  
  84.                         // Test durchführen?
  85.                         if (key.Key == ConsoleKey.J || key.Key == ConsoleKey.Enter)
  86.                         {
  87.                                 Console.Write("Fordere erhöhtes Level an… ");
  88.                                 ProcessStartInfo startInfo = new ProcessStartInfo();
  89.                                 startInfo.UseShellExecute = true;
  90.                                 startInfo.WorkingDirectory = Environment.CurrentDirectory;
  91.                                 startInfo.FileName = Assembly.GetExecutingAssembly().Location;
  92.                                 startInfo.Verb = "runas";
  93.                                 try
  94.                                 {
  95.                                         Process p = Process.Start(startInfo);
  96.                                         Console.WriteLine("OK");
  97.                                         return;
  98.                                 }
  99.                                 catch (System.ComponentModel.Win32Exception ex)
  100.                                 {
  101.                                         if (ex.NativeErrorCode == 1223)
  102.                                                 Console.WriteLine("Fehlschlag. (Abbruch durch Benutzer)");
  103.                                         else
  104.                                                 Console.WriteLine("Fehlschlag.");
  105.                                 }
  106.                         }
  107.                         else
  108.                         {
  109.                                 Console.WriteLine("Führe keinen Vererbungstest durch.");
  110.                         }
  111.                 }
  112.  
  113.                 // Tastendruck
  114.                 Console.WriteLine(Environment.NewLine + "Tastendruck zum Beenden.");
  115.                 Console.ReadKey(true);
  116.         }
  117.  
  118.         private static void TestDirectory(Environment.SpecialFolder folder)
  119.         {
  120.                 TestDirectory(new DirectoryInfo(Environment.GetFolderPath(folder)));
  121.         }
  122.  
  123.         private static void TestDirectory(DirectoryInfo directory)
  124.         {
  125.                 Console.Write("r/w-Zugriff auf \""+directory+"\": ");
  126.                 FileInfo file = new FileInfo(directory.FullName + @”\test.tmp");
  127.                 try
  128.                 {
  129.                         if (file.Exists)
  130.                                 file.Delete();
  131.                         else
  132.                         {
  133.                                 using (file.CreateText())
  134.                                 {
  135.                                 }
  136.                                 file.Delete();
  137.                         }
  138.                         Console.WriteLine("ja");
  139.                 }
  140.                 catch(UnauthorizedAccessException)
  141.                 {
  142.                         Console.WriteLine("nein (verweigert)");
  143.                 }
  144.         }
  145.  
  146.         /// <summary>
  147.         /// Gibt an, ob wir mit erhöhten Privilegien laufen
  148.         /// </summary>
  149.         /// <returns></returns>
  150.         static internal bool IsAdmin()
  151.         {
  152.                 WindowsIdentity id = WindowsIdentity.GetCurrent();
  153.                 WindowsPrincipal p = new WindowsPrincipal(id);
  154.                 return p.IsInRole(WindowsBuiltInRole.Administrator);
  155.         }
  156. }
  157. }

Das verwendete Manifest:

  1. < ?xml version="1.0" encoding="utf-8"?>
  2. <asmv1 :assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  3.   <assemblyidentity version="1.0.0.0" name="MyApplication.app"/>
  4.   <trustinfo xmlns="urn:schemas-microsoft-com:asm.v2">
  5.     <security>
  6.       <requestedprivileges xmlns="urn:schemas-microsoft-com:asm.v3">
  7.         <requestedexecutionlevel level="asInvoker" uiAccess="false" />
  8.       </requestedprivileges>
  9.     </security>
  10.   </trustinfo>
  11. </asmv1>

Frohe Jagd.

5 Antworten zu „C# Code Snippets: Vista UAC elevation”

  1. #1~Mark

    Toller Artikel, hat mir sehr geholfen. Danke!

  2. #2~gongoscho

    Hallo, auch von mir ein großes Danke :) Ich Frage hätte ich noch zum Thema UAC:

    Gibt es eine Möglichkeit nicht das ganze Programm (prozess) zu starten, sonder nur einen Teil / Methode mit Admin (erhöten) – Rechten zu starten?

    Grüße,
    gongoscho

  3. #3~Markus

    Gerne! :) Ich hab da auch lange gesucht, aber wie es aussieht: Leider nein. Das ist ein klassisches “ganz oder gar nicht”. :/

  4. #4~Ferdinand

    Super, habe nach einem einfachen (ohne PInvoke) IsElevated gesucht.

    Bin weiter auf der Suche, einen Prozess ohne elevated rights aus einem Setup (mit elevated rithts) direkt zu starten.

    Grüße, Ferdinand

  5. [...] Vista UAC Elevation (deutsch) [...]