[in]  - parametr wejściowy pobierający dane
[out] - parametr wyjściowy zwracający dane
__int16 zmienna 16 bitowa, żeby oszczędzać pamięć
taką zmienną można iniciować normalnie, czyli np: __int16 i = 0;
lub stosując suffix (przedrostek), czyli: __int16 i = 0i16; // suffix = i16
tutaj wymagana jest zmienna typu unsigned
jednak taki zapis: unsigned __int16 i = 0ui16; //przedrostek ui16
nie byłby prawidłowy, gdyż byłby to typ signed.

można też prościej: unsigned int i = 0; lub DWORD i = 0ui16;

Stosowanie zmiennej 16 bitowej nie jest tutaj konieczne,
można równie dobrze posłużyć się zwykłą zmienną int
lub unsigned int, bez stosowania suffixów.
Ta pętla czeka, aż zadanie zostanie wykonane,
przy okazji steruje obiektem ProgressBar1
Ta funkcja sprawdza tutaj tylko na jakim etapie znajduje się proces uruchamiania
Powinien mieć wartość: SERVICE_START_PENDING
jest to konieczne do zakończenia działania pętli while
bez tej funkcji tutaj pętla działałby w nieskończoność
Ta funkcja sprawdza tutaj tylko na jakim etapie znajduje się wykonywane zadanie
jest to konieczne do zakończenia działania pętli while
bez tej funkcji tutaj pętla działałby w nieskończoność
odświeżenie listy usług poprzez
odwołanie do zdarzenie OnClick
przycisku Button1 w którym powinna
znajdować się funkcja wyliczająca
usługi.

Patrz porada: Wyliczanie usług systemowych
Jeżeli wystąpi błąd zmień:
DWORD dwInfoSize = GetFileVersionInfoSize((void*)(LPCTSTR)sFileName, &dwHandlev);
Tutaj następuej pobranie numeru PID procesu z listy ProcessPID
poprzez nazwę procesu zaznaczoną na liście ListBox1
Programowanie w Borland C++ Builder

Wyliczanie usług systemu Windows.

   
Zanim rozpoczniesz wykonywanie operacji na usługach, pamiętaj, że wyłączenie usługi niezbędnej do poprawnego funkcjonowania systemu może skutkować nawet koniecznością jego ponownej instalacji, a w najlepszym wypadku uruchomienia naprawy systemu.
    Wyliczanie usług systemowych nie powinno jednak wywołać żadnych komplikacji, gdyż nie powoduje to ingerencji w ustawienia usług. Poniższy kod wylicza usługi i obrazuje je w sposób podobny do tego w konsoli services.msc, czyli będą to informacje o wyświetlanej nazwie usługi, skróconej nazwie usługi (tego nie ma w konsoli), stanie usługi (dostępnych jest siedem stanów), typie uruchamiania usługi (dostępnych jest pięć typów) oraz o sposobie logowania.
    Usługi dzielą się na dwa różne typy, usługi "programowe" (procesy) widziane w konsoli services.msc, czyli typy
SERVICE_WIN32_OWN_PROCESS (własne) i SERVICE_WIN32_SHARE_PROCESS (udziały) oraz usługi "sterownikowe", które nie są wyświetlane w konsoli, gdyż o ich stanie i sposobie uruchamia decyduje wyłącznie system, a ingerencja w te usługi może zaowocować destabilizacją systemu, są to typu SERVICE_KERNEL_DRIVER i SERVICE_FILE_SYSTEM_DRIVER.
Do wyliczania usług służy funkcja:

BOOL EnumServicesStatus(
                        SC_HANDLE             hSCManager,
                        DWORD                 dwServiceType,    
                        DWORD                 dwServiceState,
                        LPENUM_SERVICE_STATUS lpServices,
                        DWORD                 cbBufSize,
                        LPDWORD               pcbBytesNeeded,
                        LPDWORD               lpServicesReturned,
                        LPDWORD               lpResumeHandle
                       );

Parametry:
            hSCManager - [in] uchwyt do bazy danych menadżera kontroli usług. Do pobrania parametru trzeba użyć funkcji OpenSCManager.
           dwServiceType - [in] typ wyliczanych usług. Dostępne są:
SERVICE_DRIVER - usługi "sterownikowe", SERVICE_WIN32 - usługi "programowe". Chcąc wyliczyć wszystkie typy usług trzeba zastosować kombinację SERVICE_DRIVER | SERVICE_WIN32.
           dwServiceState - [in] stan wyliczanych usług. Dostępne są:
SERVICE_ACTIVE - wyliczane będą usługi których stan jest inny niż zatrzymany, SERVICE_INACTIVE - wyliczane będą tylko usługi zatrzymane, SERVICE_STATE_ALL - wyliczone zostaną wszystkie usługi niezależnie od stanu.
            lpServices - [out] wskaźnik do struktury
ENUM_SERVICE_STATUS przechowującej informacje o usłudze:

typedef struct _ENUM_SERVICE_STATUS {
                                     LPTSTR lpServiceName;
                                     LPTSTR lpDisplayName;
                                     SERVICE_STATUS ServiceStatus;
} ENUM_SERVICE_STATUS, *LPENUM_SERVICE_STATUS;

              lpServicesName - wskaźnik do zmiennej typu String przechowującej informacje o skróconej nazwie usługi
              lpDisplayName - podobnie jak wyżej, ale przechowuje informacje o nazwie wyświetlanej
              ServiceStatus - wskaźnik do struktury SERVICE_STATUS

 

typedef struct _SERVICE_STATUS {
                                DWORD dwServiceType;
                                DWORD dwCurrentState;
                                DWORD dwControlsAccepted;
                                DWORD dwWin32ExitCode;
                                DWORD dwServiceSpecificExitCode;
                                DWORD dwCheckPoint;
                                DWORD dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_STATUS;

                     Nie będę opisywał wszystkich składników tej struktury (odsyłam do plików pomocy).
                     W poradzie wykorzystamy tylko wskaźnik dwCurrentState przechowujący informacje o
                     aktualnym stanie usługi. Na szczególną zasługuje jeszcze wskaźnik dwServiceType
                     przechowujący informacje o typie usługi: SERVICE_FILE_SYSTEM_DRIVER,
                     SERVICE_KERNEL_DRIVER, SERVICE_WIN32_OWN_PROCESS, SERVICE_WIN32_SHARE_PROCESS,
                     SERVICE_INTERACTIVE_PROCESS

            cbBufSize - [in] rozmiar buforu wskaźnika struktury lpServices w bajtach. Obliczenie tego buforu wymaga dwukrotnego wywołania funkcji EnumServiceStatus, za pierwszym razem między innymi w celu obliczenia bajtów potrzebnych na przechowanie informacji o usługach. W przeciwnym razie zachodziła by konieczność zarezerwowania odpowiednio dużego bufora zdolnego pomieścić wszystkie usługi, co mogło by się okazać dość ryzykowne, np. za mały bufor.
            pcbBytesNeeded - [out] wskaźnik do zmiennej zwracającej liczbę bajtów potrzebnych do zapamiętania wyliczanych usług.
            lpServicesReturned - [out] wskaźnik do zmiennej zwracającej liczbę wyliczanych usług.
            lpResumeHandle - [out] wskaźnik do zmiennej określającej na wejściu początkowy punkt wyliczania. Przed pierwszym uruchomieniem funkcji trzeba wyzerować tą wartość.

Funkcja EnumServiesStatus zwróci nam jedynie informacje o wyświetlanej nazwie usługi, nazwie skróconej oraz o aktualnym stanie usługi.  Chcąc uzyskać informacje o typie uruchamiania i sposobie logowania usługi musimy skorzystać dodatkowo z funkcji:

BOOL QueryServiceConfig(
                        SC_HANDLE              hService,
                        LPQUERY_SERVICE_CONFIG lpServiceConfig,
                        DWORD                  cbBufSize,
                        LPDWORD                pcbBytesNeeded
                       );

Parametry:
            hService - [in] uchwyt do usługi. Do pobrania parametru trzeba użyć funkcji OpenService lub CreateService.
            lpServiceConfig - [out] wskaźnik do bufora zwracającego informacje o konfiguracji usługi. Zwracane dane są formatu
QUERY_SERVICE_CONFIG:

typedef struct _QUERY_SERVICE_CONFIG {
                                      DWORD dwServiceType;
                                      DWORD dwStartType;
                                      DWORD dwErrorControl;
                                      LPTSTR lpBinaryPathName;
                                      LPTSTR lpLoadOrderGroup;
                                      DWORD dwTagId;
                                      LPTSTR lpDependencies;
                                      LPTSTR lpServiceStartName;
                                      LPTSTR lpDisplayName;
} QUERY_SERVICE_CONFIG, *LPQUERY_SERVICE_CONFIG;

              Nie będę opisywał wszystkich elementów struktury. Istotne dla porady są dwie:
              dwStartType - przechowuje informacje o typie uruchomienia usługi
              dwServiceStartName - przechowuje informacje o sposobie logowania usługi.
              Przydatny może być również wskaźnik lpBinaryPathName przechowujący informacje o
              lokalizacji usługi wraz z parametrami uruchamiania.


            lpBufSize - [in] rozmiar bufora dla wskaźnika lpServiceConfig. Wystarczy podać wartość 4096, trzeba przy tym pamiętać o konieczności przydzielenia strukturze QUERY_SERVICE_CONFIG takiej samej ilości bajtów na stosie pamięci. Można to zrobić za pomocą funkcji LocalAlloc: (LPQUERY_SERVICE_CONFIG)LocalAlloc(LPTR, 4096), ale funkcje lokalne są wolniejsze niż inne funkcje zarządzania pamięcią, dlatego sugerowałbym raczej funkcję HeapAlloc: (LPQUERY_SERVICE_CONFIG)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_NO_SERIALIZE, 4096), różnica w szybkości działania będzie wyraźnie widoczna.
               pcbBytesNeeded - [out] wskaźnik do zmiennej zwracającej liczbę bajtów potrzebnych do zapamiętania informacji o konfiguracji usługi. Ponieważ jednak zdefiniujemy rozmiar z góry, to ten wskaźnik nie będzie odgrywał żadnej roli.

Ten opis wyjaśnia z grubsza sposób korzystania z wymienionych funkcji, teraz pozostało tylko połączyć wszystkie elementy w jedną spójną całość. Do wyświetlenia listy z usługami posłużymy się komponentem TLisView, najlepiej się do tego nadaje gdyż oprócz wierszy może zawierać również kolumny. Umieszczamy na formularzu komponent ListView1, przechodzimy do jego właściwości Columns i tworzymy pięć kolumn o nazwach: Wyświetlana nazwa usługi; Nazwa usługi; Stan usługi; Typ uruchomienia; Logowanie jako. Jak widać nazwy są identyczne jak konsoli services.msc, jedyną różnicą jest Nazwa usługi gdzie będzie wyświetlana skrócona nazwa usługi, co się bardzo przyda w dalszej części porady. Kolejną właściwością ListView1 jest ViewStyle i ustawiamy ją na vsRaport. Bardzo ważna uwaga, właściwość SortType musi być ustawiona na stNone, gdyż sortowanie pomieszałoby w procesie wyliczania wiersze z kolumnami i nic by się nie zgadzało. Niemniej jednak sortowanie listy będzie się odbywało jednak będzie ono sterowane poprzez kod, a konkretnie przed rozpoczęciem wyliczania SortType będzie zawsze ustawiane na stNone, a po zakończeniu wyliczania na stText co posortuje usługi w kolejności alfabetycznej. Umieszczamy jeszcze na formularzu komponent Button1 i w zdarzeniu OnCLick umieszczamy następujący kod:

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 ENUM_SERVICE_STATUS lpESS, *lpService;
 SERVICE_STATUS lpSStat;
 LPQUERY_SERVICE_CONFIG lqSC = (LPQUERY_SERVICE_CONFIG)HeapAlloc(GetProcessHeap(),
                                HEAP_ZERO_MEMORY | HEAP_NO_SERIALIZE, 4096);
 DWORD lp1, lpCount, lp3 = 0;
 SC_HANDLE sc = ::OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
 BOOL retVal = false;

 ListView1->Items->Clear();
 ListView1->SortType = Comctrls::stNone;

 if(sc != NULL)
 {
  retVal = EnumServicesStatus(sc, SERVICE_WIN32, SERVICE_STATE_ALL, &lpESS,
                              sizeof(lpESS), &lp1, &lpCount, &lp3);

  DWORD err = GetLastError();

  if((retVal == FALSE) || err == ERROR_MORE_DATA)
  {
   DWORD dwBytes = lp1 + sizeof(ENUM_SERVICE_STATUS);
   lpService = new ENUM_SERVICE_STATUS[dwBytes];
   EnumServicesStatus(sc, SERVICE_WIN32, SERVICE_STATE_ALL, lpService,
                      dwBytes, &lp1, &lpCount, &lp3);

  }

  for(unsigned i = 0ui16; i < lpCount; i++)
  {
   lpSStat = lpService[i].ServiceStatus;
   String status = "";
  
   switch(lpSStat.dwCurrentState)
   {
    case 1: status = "Zatrzymano"; break;
    case 2: status = "Trwa uruchamianie"; break;
    case 3: status = "Trwa zatrzymywanie"; break;
    case 4: status = "Uruchomiono"; break;
    case 5: status = "Trwa wznawianie"; break;
    case 6: status = "Trwa wstrzymywanie"; break;
    case 7: status = "Wstrzymano"; break;
   }
 
   DWORD dwBytesNeeded;
   SC_HANDLE scService = ::OpenService(sc, lpService[i].lpServiceName, SERVICE_ALL_ACCESS);
   QueryServiceConfig(scService, lqSC, 4096, &dwBytesNeeded);

   String start = "";
   switch(lqSC->dwStartType)
   {
    case 0: start = "Boot"; break;
    case 1: start = "System"; break;
    case 2: start = "Automatyczy"; break;
    case 3: start = "Ręczny"; break;
    case 4: start = "Wyłączony"; break;
   }

   ListView1->Items->Add();
   ListView1->Items->Item[i]->Caption = (String)lpService[i].lpDisplayName;
   ListView1->Items->Item[i]->SubItems->Add((String)lpService[i].lpServiceName);
   ListView1->Items->Item[i]->SubItems->Add(status);
   ListView1->Items->Item[i]->SubItems->Add(start);
   if((String)lqSC->lpServiceStartName == "LocalSystem")
      ListView1->Items->Item[i]->SubItems->Add("System lokalny");
   else if(((String)lqSC->lpServiceStartName).Pos("LocalService") > 0)
           ListView1->Items->Item[i]->SubItems->Add("Usługa lokalna");
        else if(((String)lqSC->lpServiceStartName).Pos("NetworkService") > 0)
                ListView1->Items->Item[i]->SubItems->Add("Usługa sieciowa");
             else ListView1->Items->Item[i]->SubItems->Add((String)lqSC->lpServiceStartName);
  }
  ListView1->SortType = Comctrls::stText;
 }
 CloseServiceHandle(sc);
}
//--------------------------------

Przedstawiony wyżej kod można uprościć, rezygnując na przykład z obliczania rozmiaru struktury dla wyliczanych usług, poprzez zdefiniowanie jej rozmiaru na 512, można również zrezygnować z instrukcji switch zastępując ją tablicą typy String:

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 ENUM_SERVICE_STATUS lpService[512];
 SERVICE_STATUS lpSStat;
 LPQUERY_SERVICE_CONFIG lqSC = (LPQUERY_SERVICE_CONFIG)HeapAlloc(GetProcessHeap(),
                                HEAP_ZERO_MEMORY | HEAP_NO_SERIALIZE, 4096);
 DWORD lp1, lpCount, lp3 = 0;
 SC_HANDLE sc = ::OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);

 ListView1->Items->Clear();
 ListView1->SortType = Comctrls::stNone;

 if(sc != NULL)
 {
  DWORD dwBytes = 512 * sizeof(ENUM_SERVICE_STATUS);
  if(EnumServicesStatus(sc, SERVICE_WIN32, SERVICE_STATE_ALL, lpService, dwBytes, &lp1, &lpCount, &lp3))
  {
   for(unsigned i = 0ui16; i < lpCount; i++)
   {
    lpSStat = lpService[i].ServiceStatus;
    String status[] = {"", "Zatrzymano", "Trwa uruchamianie", "Trwa zatrzymywanie",
              "Uruchomiono", "Trwa wznawianie", "Trwa wstrzymywanie", "Wstrzymano"};

    DWORD dwBytesNeeded;
    SC_HANDLE scService = ::OpenService(sc, lpService[i].lpServiceName, SERVICE_ALL_ACCESS);
    QueryServiceConfig(scService, lqSC, 4096, &dwBytesNeeded);

    String start[] = {"Boot", "System", "Automatyczny", "Ręczny", "Wyłączony"};

    ListView1->Items->Add();
    ListView1->Items->Item[i]->Caption = (String)lpService[i].lpDisplayName;
    ListView1->Items->Item[i]->SubItems->Add((String)lpService[i].lpServiceName);
    ListView1->Items->Item[i]->SubItems->Add(status[lpSStat.dwCurrentState]);
    ListView1->Items->Item[i]->SubItems->Add(start[lqSC->dwStartType]);

    if((String)lqSC->lpServiceStartName == "LocalSystem")
       ListView1->Items->Item[i]->SubItems->Add("System lokalny");
    else if(((String)lqSC->lpServiceStartName).Pos("LocalService") > 0)
            ListView1->Items->Item[i]->SubItems->Add("Usługa lokalna");
         else if(((String)lqSC->lpServiceStartName).Pos("NetworkService") > 0)
                 ListView1->Items->Item[i]->SubItems->Add("Usługa sieciowa");
              else ListView1->Items->Item[i]->SubItems->Add((String)lqSC->lpServiceStartName);
   }
   ListView1->SortType = Comctrls::stText;
  }
 }
 CloseServiceHandle(sc);
}

Pozostała kwestia opisu usługi. Znając skróconą nazwę usługi możemy pobrać sobie jej opis bezpośrednio z rejestru, w ten sam sposób można pobrać wszystkie informacje o usłudze, ale skupimy się tylko na opisie. Pod komponentem ListView1 umieszczamy na formularzu komponent Memo1, gdyż to w nim będzie pojawiał się opis dla wybranej w ListView1 usługi. By móc skorzystać z klasy TRegistry trzeba do pliku nagłówkowego (np. Unit1.h) włączyć bibliotekę Registry.hpp. Tworzymy zdarzenie OnSelectItem dla komponentu ListView1 i umieszczamy w nim taki kod:

#include <Registry.hpp>
void __fastcall
TForm1::ListView1SelectItem(TObject *Sender, TListItem *Item,
      bool Selected)
{
 if(Selected)
 {
  Memo1->Lines->Clear();
  String service = Item->SubItems->Strings[0]; // skrócona nazwa usługi znajduje się w pierwszym SubItem
 
  TRegistry *Rejestr = new TRegistry();
  Rejestr->RootKey = HKEY_LOCAL_MACHINE;
  Rejestr->OpenKey("SYSTEM\\CurrentControlSet\\Services\\" + service, false);
  Memo1->Lines->Add(Rejestr->ReadString("Description"));
  Rejestr->Free();
 }
}

...powrót do menu. 

Zatrzymywanie, uruchamianie, wstrzymywanie i wznawianie usług.

   
Zanim rozpoczniesz wykonywanie operacji na usługach, pamiętaj, że wyłączenie usługi niezbędnej do poprawnego funkcjonowania systemu może skutkować nawet koniecznością jego ponownej instalacji, a w najlepszym wypadku uruchomienia naprawy systemu.
    Ta porada jest kontynuacją porady Wyliczanie usług systemu Windows, dlatego najpierw trzeba się zapoznać z tamtą poradą. Ci którzy już to zrobili powinni sobie uruchomić teraz projekt z tamtej porady, gdyż będę się w całości opierał na tym co zostało wcześniej zrobione.
    Do zatrzymywania, wstrzymywania i wznawiania usługi służy funkcja:

BOOL ControlService(
                    SC_HANDLE hService,
                    DWORD dwControl,
                    LPSERVICE_STATUS lpServiceStatus
                   );

Parametry:
            hService - [in] uchwyt do usługi. Do pobrania parametru trzeba użyć funkcji OpenService lub CreateService.
           dwControl - [in] kod operacji jaką chcemy wykonać na usłudze. Dostępne jest 9 wartości, ja jednak podam tylko trzy z których będziemy korzystać:
SERVICE_CONTROL_STOP - polecenie nakazujące zatrzymanie usługi, SERVICE_CONTROL_PAUSE - polecenie nakazujące wstrzymanie usługi, jeżeli to polecenie zostanie wysłane do usługi, która jest już wstrzymana, to zostanie ona wznowiona, SERVICE_CONTROL_CONTINUE - polecenie nakazujące wznowienie usługi.
           lpServiceStatus - [out] wskaźnik do struktury SERVICE_STATUS

typedef struct _SERVICE_STATUS {
                                DWORD dwServiceType;
                                DWORD dwCurrentState;
                                DWORD dwControlsAccepted;
                                DWORD dwWin32ExitCode;
                                DWORD dwServiceSpecificExitCode;
                                DWORD dwCheckPoint;
                                DWORD dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_STATUS;

              Nie będę opisywał wszystkich składników tej struktury (odsyłam do plików pomocy).
              W poradzie wykorzystamy tylko wskaźnik dwCurrentState przechowujący informacje o
              aktualnym stanie usługi.
              Zwracam jednak uwagę na parametr dwWaintHint, który zwraca informacje o czasie
              potrzebnym do zamknięcia operacji w milisekundach, co może być pomocne, przy
              obrazowaniu procesu za pomocą ProgressBar.

W poradzie wykorzystam do obrazowania operacji ProgressBar, ale nie ten odmierzający czas od początku do końca, lecz taki: , dlatego konieczne jest umieszczenie manifestu w programie, w przeciwnym razie nie będzie to działać.  Jedynym nowym elementem na razie będzie wymieniona wyżej funkcja, dlatego nie będę niczego więcej na razie opisywał. Mając projekt z poprzedniej porady, pod zdarzeniem OnSelectItem (nie w zdarzeniu) tworzymy nową funkcję:

//---------------------------------------------------------------------------
DWORD ChangeServiceStatus(DWORD sControl, TProgressBar *ProgressBar,
      TListView *ListView)
{
 SC_HANDLE scManager, scService;
 SERVICE_STATUS sSS;
 DWORD sPending;
 __int16 c = ListView->Selected->Index;

 switch(sControl)
 {
  case SERVICE_CONTROL_STOP:     sPending = SERVICE_STOP_PENDING; break;
  case SERVICE_CONTROL_PAUSE:    sPending = SERVICE_PAUSE_PENDING; break;
  case SERVICE_CONTROL_CONTINUE: sPending = SERVICE_CONTINUE_PENDING; break;
 }
 String servName = ListView->Items->Item[c]->SubItems->Strings[0];

 String status[] = {"", "Zatrzymano", "Trwa uruchamianie", "Trwa zatrzymywanie", "Uruchomiono", "Trwa wznawianie", "Trwa wstrzymywanie", "Wstrzymano"};

 if((scManager = ::OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE)) != NULL)
 {
  if((scService = ::OpenService(scManager, servName.c_str(), SERVICE_ALL_ACCESS)) != NULL)
  {
   if(ControlService(scService, sControl, &sSS) != 0)
   {
    long style = GetWindowLong(ProgressBar->Handle, GWL_STYLE);
    long oldstyle = GetWindowLong(ProgressBar->Handle, GWL_STYLE);
    style = style | 0x08;
    SetWindowLong(ProgressBar->Handle, GWL_STYLE, style);
    ProgressBar->Perform((WM_USER + 10), true, 100);


    while(sSS.dwCurrentState == sPending)
    {
     ControlService(scService, SERVICE_CONTROL_INTERROGATE, &sSS);
     ProgressBar->Position += 1;
     Sleep(10);
    }
    ProgressBar->Perform((WM_USER + 10), false, 100);
    SetWindowLong(ProgressBar->Handle, GWL_STYLE, oldstyle);

    ListView->Items->Item[c]->SubItems->Strings[1] = status[sSS.dwCurrentState];
   }
  }
 }
 ListView->Selected->Selected = false;
 ListView->ItemIndex = c;
 ListView->SetFocus();
 return sSS.dwCurrentState;
}
//--------------------------------

Teraz przygotowujemy formularza. Przypominam, że bazuję na projekcie z poprzedniej porady, więc komponent ListView1 znajduje się już na formularzu podobnie jego jego zdarzenie OnSelectItem. Umieszczamy na formularzu cztery przyciski: Button3 - Caption = Zatrzymaj; Button4 - Caption =  Uruchom; Button5 - Caption = Wstrzymaj; Button6 - Caption = Wznów. Właściwość Enabled wszystkich czterech komponentów ustawiamy na false. Pod komponentem ListView1, a nad komponentem Memo1 umieszczamy ProgressBar1. Następnie dodajemy nowy kod do zdarzenia OnSelectItem obiektu ListView1:

void __fastcall TForm1::ListView1SelectItem(TObject *Sender, TListItem *Item,
      bool Selected)
{
 if(Selected)
 {
  Memo1->Lines->Clear();
  String service = Item->SubItems->Strings[0];

  TRegistry *Rejestr = new TRegistry();
  Rejestr->RootKey = HKEY_LOCAL_MACHINE;
  Rejestr->OpenKey("SYSTEM\\CurrentControlSet\\Services\\" + service, false);
  Memo1->Lines->Add(Rejestr->ReadString("Description"));
  Rejestr->Free();


  String servState = Item->SubItems->Strings[1];
  String servTyp   = Item->SubItems->Strings[2];

  if((servTyp != "Ręczny") && (servTyp != "Automatyczny"))
  {
   Button3->Enabled = false; // zatrzymaj
   Button4->Enabled = false; // uruchom
   Button5->Enabled = false; // wstrzymaj
   Button6->Enabled = false; // wznów
  }
  else
  {
   if(servState == "Uruchomiono")
   {
    Button3->Enabled = true;
    Button4->Enabled = false;
    Button5->Enabled = true;
    Button6->Enabled = false;
   }
   if(servState == "Zatrzymano")
   {
    Button3->Enabled = false;
    Button4->Enabled = true;
    Button5->Enabled = false;
    Button6->Enabled = false;
   }
   if(servState == "Wstrzymano")
   {
    Button3->Enabled = false;
    Button4->Enabled = false;
    Button5->Enabled = false;
    Button6->Enabled = true;
   }
  }
 }
}

Tworzymy zdarzenia OnClick dla przycisków Button3, Button5 i Button6. W każdym zdarzeniu zostanie wywołana funkcja ChangeServiceStatus z innym poleceniem. Polecenia będą wydawane dla usługi zaznaczonej na liście ListView i w zależności od aktualnego stanu usługi i typu uruchomienia będą się aktywowały lub blokowały odpowiednie przyciski zgodnie z ustawieniami w zdarzeniu OnSelectItem:

void __fastcall TForm1::Button3Click(TObject *Sender)
{
 DWORD stat;
 stat = ChangeServiceStatus(SERVICE_CONTROL_STOP, ProgressBar1, ListView1);
 switch(stat)
 {
  case 1: Button3->Enabled = false; break;
  case 4: Button3->Enabled = true; break;
 }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button5Click(TObject *Sender)
{
 DWORD stat;
 stat = ChangeServiceStatus(SERVICE_CONTROL_PAUSE, ProgressBar1, ListView1);
 switch(stat)
 {
  case 1: Button5->Enabled = false; break;
  case 4: Button5->Enabled = true; break;
  case 7: Button5->Enabled = false; break;
 }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button6Click(TObject *Sender)
{
 DWORD stat;
 stat = ChangeServiceStatus(SERVICE_CONTROL_CONTINUE, ProgressBar1, ListView1);
 switch(stat)
 {
  case 1: Button6->Enabled = false; break;
  case 4: Button6->Enabled = false; break;
  case 7: Button6->Enabled = true; break;
 }
}

Zatrzymywanie, wstrzymywanie i wznawianie usługi już działa, teraz pozostało zrobienie uruchamiania usługi. Służy do tego funkcja:

BOOL StartService(
                  SC_HANDLE hService,
                  DWORD dwNumServiceArgs,
                  LPCTSTR* lpServiceArgVectors
                 );

Parametry:
            hService - [in] uchwyt do usługi. Do pobrania parametru trzeba użyć funkcji OpenService lub CreateService.
           dwNumServiceArgs - [in] liczba znaków w tablicy lpServiceArgVectors. Jeżeli tablica jest pusta ten parametr może mieć wartość 0.
           lServiceArgVectors - [in] wskaźnik do tablicy.

Jak widać funkcja jest banalnie prosta w użyciu. Wystarczy ją po prostu wywołać z jednym argumentem wypełnionym przez funkcję OpenService otwierającą dostęp do usługi, pozostałe dwa argumenty pozostają puste. Dlatego też funkcja zostanie w całości wywołana w zdarzeniu OnClick przycisku Button4:

void __fastcall TForm1::Button4Click(TObject *Sender)
{
 SC_HANDLE scManager, scService;
 SERVICE_STATUS sSS;
 __int16 c = ListView1->Selected->Index;
 String servName = ListView1->Items->Item[c]->SubItems->Strings[0];

 String status[] = {"", "Zatrzymano", "Trwa uruchamianie", "Trwa zatrzymywanie", "Uruchomiono", "Trwa wznawianie", "Trwa wstrzymywanie", "Wstrzymano"};

 if((scManager = ::OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE)) != NULL)
 {
  if((scService = ::OpenService(scManager, servName.c_str(), SERVICE_ALL_ACCESS)) != NULL)
  {
   if(StartService(scService, 0, NULL) != 0)
   {
    long style = GetWindowLong(ProgressBar1->Handle, GWL_STYLE);
    long oldstyle = GetWindowLong(ProgressBar1->Handle, GWL_STYLE);
    style = style | 0x08;
    SetWindowLong(ProgressBar1->Handle, GWL_STYLE, style);
    ProgressBar1->Perform((WM_USER + 10), true, 100);


    ControlService(scService, SERVICE_CONTROL_INTERROGATE, &sSS);

    while(sSS.dwCurrentState == SERVICE_START_PENDING)
    {
     ControlService(scService, SERVICE_CONTROL_INTERROGATE, &sSS);
     ProgressBar1->Position += 1;
     Sleep(10);
    }

    ProgressBar1->Perform((WM_USER + 10), false, 100);
    SetWindowLong(ProgressBar1->Handle, GWL_STYLE, oldstyle);

    ListView1->Items->Item[c]->SubItems->Strings[1] = status[sSS.dwCurrentState];
   }
  }
 }
 ListView1->Selected->Selected = false;
 ListView1->ItemIndex = c;
 ListView1->SetFocus();
}

To powinno działać. U mnie działa prawidłowo. Trzeba by tylko jeszcze popracować nad obrazowaniem procesu w ProgressBar, ale może się to okazać trudne, gdyż z moich testów wynikało, że większość operacji na usługach nie trwa dłużej niż 500 miliskund.
Jeżeli ktoś lubi to może sobie włączyć kolorowanie linii, np. zaznaczenie na szaro zatrzymanych usług. Trzeba się w tym celu posłużyć zdarzeniem OnAdvancedCustomDrawItem obiektu LisView1:

void __fastcall TForm1::ListView1AdvancedCustomDrawItem(
      TCustomListView *Sender, TListItem *Item, TCustomDrawState State,
      TCustomDrawStage Stage, bool &DefaultDraw)
{
 if(Item->SubItems->Strings[1] == "Zatrzymano")
 {
  ListView1->Canvas->Brush->Color = clSilver;
  //ListView1->Canvas->Font->Color = clWhite;
 }
}

...powrót do menu. 

Modyfikowanie ustawień usługi.

   
Zanim rozpoczniesz wykonywanie operacji na usługach, pamiętaj, że wyłączenie usługi niezbędnej do poprawnego funkcjonowania systemu może skutkować nawet koniecznością jego ponownej instalacji, a w najlepszym wypadku uruchomienia naprawy systemu.
    Ta porada jest kontynuacją porady Wyliczanie usług systemu Windows, dlatego najpierw trzeba się zapoznać z tamtą poradą. Ci którzy już to zrobili powinni sobie uruchomić teraz projekt z tamtej porady, gdyż będę się w całości opierał na tym co zostało wcześniej zrobione.
    Do modyfikowanie usługi służy funkcja:

BOOL ChangeServiceConfig(
  			 SC_HANDLE hService,
  			 DWORD     dwServiceType,
  			 DWORD     dwStartType,
  			 DWORD     dwErrorControl,
  			 LPCTSTR   lpBinaryPathName,
  			 LPCTSTR   lpLoadOrderGroup,
  			 LPDWORD   lpdwTagId,
  			 LPCTSTR   lpDependencies,
  			 LPCTSTR   lpServiceStartName,
  			 LPCTSTR   lpPassword,
  			 LPCTSTR   lpDisplayName
			);

Parametry:
            hService - [in] uchwyt do usługi. Do pobrania parametru trzeba użyć funkcji OpenService lub CreateService.
            dwServiceType - [in] typ usługi "programowa",  "sterownikowa", jeżeli nie zmieniamy podajemy wartość SERVICE_NO_CHANGE
           dwStartType - [in] typ uruchomienia usługi: SERVICE_AUTO_START - automatyczny; SERVICE_BOOT_START - boot (tylko usługi sterownikowe); SERVICE_DEMAND_START - ręczny; SERVICE_DISABLED - wyłączony; SERVICE_SYSTEM_START - uruchamiana przez system (tylko usługi sterownikowe). Jeżeli nie zmieniamy podajemy wartość SERVICE_NO_CHANGE.
           
dwErrorControl - [in] kontrola blędów, czyli co ma zrobić funkcja, jeżeli przestawienie usługi się nie powiodło. Jeżeli nie zmieniamy podajemy wartość SERVICE_NO_CHANGE.
           
lpBinaryPathName - [in] ścieżka dostępu wraz z argumentami do usługi,  np: "\"d:\\my share\\myservice.exe\"". Jeżeli nie zmieniamy podajemy wartość NULL.
            lpLoadOrderGroup - [in] przyporządkowanie do grupy. Jeżeli nie zmieniamy podajemy wartość NULL.
            lpdwTagId - [out] identyfikator tag, unikalny w grupie. Jeżeli nie zmieniamy podajemy wartość NULL.
           
lpDependencies
- [in] tabela zależności usług, powiązania z innymi usługami. Jeżeli nie zmieniamy podajemy wartość NULL.
            lpServiceStartName - [in] skrócona nazwa usługi, czyli nazwa dostępowa, jest to nazwa klucza usługi w rejestrze. Jeżeli nie zmieniamy podajemy wartość NULL.
           
lpPassword
- [in] hasło dostępowe do usługi. Jeżeli nie zmieniamy podajemy wartość NULL.
           
lpDisplayName
- [in] wyświetlana nazwa usługi, nazwa opisowa. Jeżeli nie zmieniamy podajemy wartość NULL.

Modyfikacja usług jest niebezpieczna, gdyż wprowadzenie zmian do usługi istotnej dla prawidłowego funkcjonowania systemu, może sprawić, że nie uruchomimy go ponownie, nawet w trybie awaryjny. Pozostaje wtedy tylko włączenie naprawy systemu z wykorzystaniem płyty instalacyjnej, ale to jest dobre tylko na krótką metę, gdyż po takiej naprawie system z reguły nie pracuje już w pełni funkcjonalnie.
Wszelkich zmian w usługach dokonujesz na własne ryzyko, ja nie biorę na siebie żadnej odpowiedzialności za błędy i szkody jakie może to spowodować, jeżeli tego nie akceptujesz, to nie czytaj porady, gdyż nie jest ona dla ciebie. Usługi których nie należy modyfikować.

Żeby ograniczyć szkody jakie mogą powstać w wyniku nieodpowiedzialnego modyfikowania usług, przedstawiony niżej kod będzie modyfikował tylko typ uruchomienia, poza tym wprowadziłem do niego ograniczenie blokujące modyfikowanie ważnych usług systemowych. Modyfikowana będzie usługa wybrana z listy ListView1, która została utworzona w poradzie wyliczanie usług systemowych.

//---------------------------------------------------------------------------
void __fastcall TForm1::Button7Click(TObject *Sender)
{
 SC_HANDLE scManager, scService;
 __int16 c = ListView1->Selected->Index;
 String servName = ListView1->Items->Item[c]->SubItems->Strings[0];

 AnsiString importantServices[] = {"eventlog", "winmgmt", "dhcp", "dnscache",
           "protectedstorage", "samss", "plugplay", "dcomlaunch", "cryptsrc",
           "shellhwdetection", "rpcss"}; // lista usług w których nie należy nic zmieniać

 const __int8 count = ARRAYSIZE(importantServices) - 1;

 for(__int16 i = 0; i <= count; i++)
 {
  if(importantServices[i].LowerCase() == servName.LowerCase())
  {
   Application->MessageBox("Zablokowano możliwość modyfikacji tej usługi ze względów bezpieczeństwa",
                           "Blokada modyfikacji", MB_OK | MB_ICONSTOP);
   return;
  }
 }

 if((scManager = ::OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE)) != NULL)
 {
  if((scService = ::OpenService(scManager, servName.c_str(), SERVICE_ALL_ACCESS)) != NULL)
  {
   if(ChangeServiceConfig(
                          scService,
                          SERVICE_NO_CHANGE, // typ usługi: "programowe", "sterownikowe"
                          SERVICE_DISABLED, // typ uruchomienia tutaj ustawiony na wyłączony
                          SERVICE_NO_CHANGE, // typ błędy, czyli jak ma postąpić program w razie niemożności uruchomienia usługi
                          NULL, // nowa ścieżka dostępu do pliku usługi wraz z argumentami
                          NULL, // przyporządkowanie do grupy
                          NULL, // identyfikator tag unikalny w danej grupie
                          NULL, // tabela zależności usług, powiązania z innymi usługami
                          NULL, // skrócona nazwa usługi, czyli nazwa dostępowa usługi
                          NULL, // hasło dostępowe do usługi
                          NULL  // wyświetlana nazwa usługi (nazwa opisowa)
                         ))
   {
    ListView1->Items->Item[c]->SubItems->Strings[2] = "Wyłączony";
   }
  }
 }
 ListView1->Selected->Selected = false;
 ListView1->ItemIndex = c;
 ListView1->SetFocus();
}
//---------------------------------------------------------------------------

W przykładzie usługa wybrana z listy zostanie przestawiona w tryb wyłączenia. Ustawienie trybu uruchomienia na wyłączony, nie spowoduje wyłączenia usługi, przy ponownym uruchomieniu komputera nie będzie ona włączana, podobnie przestawienie usługi w tryb automatyczny nie spowoduje jej uruchomienia, będzie ona jednak uruchamiana automatycznie przy każdym uruchomieniu komputera. Testy proponuję przeprowadzać na jakichś mało istotnym usługach jak np. "Aktualizacje automatyczne".

...powrót do menu. 

Usuwanie usługi.

   
Zanim rozpoczniesz wykonywanie operacji na usługach, pamiętaj, że wyłączenie usługi niezbędnej do poprawnego funkcjonowania systemu może skutkować nawet koniecznością jego ponownej instalacji, a w najlepszym wypadku uruchomienia naprawy systemu.
    Ta porada jest kontynuacją porady Wyliczanie usług systemu Windows, dlatego najpierw trzeba się zapoznać z tamtą poradą. Ci którzy już to zrobili powinni sobie uruchomić teraz projekt z tamtej porady, gdyż będę się w całości opierał na tym co zostało wcześniej zrobione.
    Do usuwania usługi służy funkcja:

BOOL DeleteService(
  		   SC_HANDLE hService,
		  );

Parametry:
            hService - [in] uchwyt do usługi. Do pobrania parametru trzeba użyć funkcji OpenService lub CreateService.


Usługi kasujesz na własne ryzyko, ja nie biorę na siebie żadnej odpowiedzialności za błędy i szkody jakie może to spowodować, jeżeli tego nie akceptujesz, to nie czytaj porady, gdyż nie jest ona dla ciebie.

Żeby ograniczyć szkody jakie mogą powstać w wyniku usunięcia usługi istotnej dla funkcjonowania systemu, wprowadziłem do przedstawionego niżej kodu ograniczenie blokujące usuwanie ważnych usług systemowych. Usuwana będzie usługa wybrana z listy ListView1, która została utworzona w poradzie wyliczanie usług systemowych.

Usługi których nie należy usuwać pod żadnym pozorem:

 

//---------------------------------------------------------------------------
void __fastcall TForm1::Button8Click(TObject *Sender)
{
 SC_HANDLE scManager, scService;
 __int16 c = ListView1->Selected->Index;
 String servName = ListView1->Items->Item[c]->SubItems->Strings[0];
 String DisplayName = ListView1->Items->Item[c]->Caption;

 AnsiString importantServices[] = {"wuauserv", "spooler", "eventlog", "winmgmt",
  "dhcp", "dnscache", "trkwks", "themes", "protectedstorage", "dmserver", "samss",
  "plugplay", "lmhosts", "dcomlaunch", "browser", "lanmanserver", "lanmanworkstation",
  "srservice", "policyagent", "cryptsrc", "audiosrv", "shellhwdetection", "sharedaccess",
  "rpcss"}; // lista usług, których nie można usunąć

 const __int8 count = ARRAYSIZE(importantServices) - 1;

 for(__int16 i = 0; i <= count; i++)
 {
  if(importantServices[i].LowerCase() == servName.LowerCase())
  {
   Application->MessageBox("Zablokowano możliwość usunięcia tej usługi ze względów bezpieczeństwa",
                           "Blokada usunięcia", MB_OK | MB_ICONSTOP);
   return;
  }
 }

 if((scManager = ::OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE)) != NULL)
 {
  if((scService = ::OpenService(scManager, servName.c_str(), SERVICE_ALL_ACCESS)) != NULL)
  {
   __int8 id = Application->MessageBox(("Czy na pewno chcesz usunąć usługę: '" +
               DisplayName + "'?\nMoże to wpłynąć niekorzystnie na pracę systemu").c_str(),
               "Usuwanie usługi", MB_YESNO | MB_ICONEXCLAMATION);
   if(id == ID_NO) return;
   else
   {
    if(DeleteService(scService))
    {
     Application->MessageBox(("Usługa '" + DisplayName + "' została pomyślnie usunięta!").c_str(),
                              "Usunięto usługę", MB_OK | MB_ICONINFORMATION);
     Sleep(2000);
     Button1Click(Sender);
    }
    else
     Application->MessageBox(("Usuwanie usługi " + DisplayName + " zakończone niepowodzeniem!").c_str(), "Nie można usunąć", MB_OK | MB_ICONSTOP);
   }
  }
 }
}
//---------------------------------------------------------------------------

Podczas instalacji niektóre programy dorzucają własne usługi, których zadaniem jest z reguły sprawdzanie dostępności aktualizacji. Takie usługi są na ogół zbędne i lepiej jest je wyłączyć lub nawet usunąć. U mnie np. podczas instalacji QuickTime Player v7.1 zainstalowała się usługa Bonjour Service z dziwaczną nazwą ##Id_String1.6844F930_1628_4223_B5CC_5BB94B879762##. Usunąłem ją za pomocą powyższego kodu, bez uszczerbku dla systemu.

...powrót do menu. 

Pobieranie informacji o wersji pliku.

    Pliki programów (exe), rozszerzenia aplikacji (dll), rozszerzenia panelu sterowania (cpl) itp, w większości przypadków posiadają między innymi informacje o wersji pliku. Wszystkie standardowe i niestandardowe informacje przechowywane w takim pliku można odczytać.

W bibliotekach BCB znajduje się funkcja VerQueryValue umożliwiająca odczytanie tych informacji. W tej poradzie przedstawię przykłady kilku funkcji (wykorzystujących wspomnianą wcześniej funkcję) pobierających informacje o wersji pliku. Nie będę już tutaj opisywał specyfikacji tej funkcji, gdyż wszystko znajduje się w plikach pomocy BCB i byłoby to po prostu przepisywanie.
Na początek przedstawię przykład funkcji ogólnej odczytującej informacje o wersji pliku i zwracającej je do klasy typu TStrings. Jedynym uzasadnieniem użycia tutaj funkcji ogólnej jest możliwość wyświetlenia odczytywanych informacji w obiektach posiadających klasę TString, czyli TMemo, TListBox, TComboBox, TRichEdit. Konstrukcja funkcji ogólnej umożliwia zwracanie tych wartości niezależnie od typu obiektu. Można by to oczywiście rozwiązać na wiele innych sposobów, ale po prostu przy okazji przypomnę - tym którzy już zapomnieli - jak działają funkcje ogólne.

W prezentowanym przykładzie funkcja GetAllFileVersionInformation będzie odczytywała dziesięć standardowych informacji o wersji pliku i jedną informację dodatkową o nazwie klucza Cyfbar. Funkcja niezależnie od tego ile informacji będzie odczytywać, to i tak zwróci jako wynik tylko te, które udało się odczytać. Jak łatwo się domyślić żaden plik nie będzie posiadała klucza o nazwie Cyfbar, ale tworząc własny program można w opcjach projektu  (Project | Options... w sekcji Version info) dodać własne klucze i jak tak właśnie zrobiłem tworząc klucz o nazwie Cyfbar. Znając nazwę klucza można odczytać przypisaną mu wartość, czyli idealnie by było gdybyśmy mogli odczytać wszystkie klucze, a potem przypisane im wartości. Niestety na razie nie wiem jak wyliczyć klucze, dlatego w tej poradzie wszystkie nazwy kluczy trzeba będzie określić jawnie, przy czym nazwy kluczy standardowych są stałe:

#include <stdio.h>
//--------------------------------

template <class T> void GetAllFileVersionInformation(char *ModulePath, T *Memo)
{
 const AnsiString KeyName[] = {"CompanyName", "FileDescription", "FileVersion",
       "InternalName", "LegalCopyright", "LegalTradeMarks", "OriginalFileName",
                        "ProductName", "ProductVersion", "Comments", "Cyfbar"};

 const AnsiString plInfoStr[] = {"Firma", "Opis", "Wersja pliku",
                "Nazwa wewnętrzna", "Prawa autorskie", "Prawne znaki towarowe",
    "Oryginalna nazwa pliku", "Nazwa Produktu", "Wersja Produktu", "Komentarz",
                                             "Pole dodatkowe o nazwie Cyfbar"};

 LPVOID lpStr1 = NULL, lpStr2 = NULL;
 WORD* wTmp;
 DWORD dwHandlev = NULL;
 UINT dwLength;
 char sFileName[1024] = {0};
 char sTmp[1024] = {0};
 LPVOID* pVersionInfo;

 if(ModulePath == NULL) GetModuleFileName(NULL, sFileName, 1024); // jeżeli NULL pobierz ścieżkę dostępu do tego pliku
 else strcpy(sFileName, ModulePath);

 DWORD dwInfoSize = GetFileVersionInfoSize((LPSTR)sFileName, &dwHandlev);
 if(dwInfoSize)
 {
  pVersionInfo = new LPVOID[dwInfoSize];
  if(GetFileVersionInfo((char*)(LPCTSTR)sFileName, dwHandlev, dwInfoSize, pVersionInfo))
  {
   if(VerQueryValue( pVersionInfo, "\\VarFileInfo\\Translation", &lpStr1, &dwLength))
   {
    wTmp = (WORD*)lpStr1;

    for(int i = 0; i < 11; i++)
    {
     sprintf(sTmp, ("\\StringFileInfo\\%04x%04x\\" + KeyName[i]).c_str(), *wTmp, *(wTmp + 1));

     if(VerQueryValue(pVersionInfo, sTmp, &lpStr2, &dwLength))
     {
      TVarRec vr[] = {plInfoStr[i], (LPSTR)lpStr2};
      Memo->Add(Format("%s: %s", vr, 2));
     }
    }

   }
  }
  delete[] pVersionInfo;
 }
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 String ExeName = "c:\\Program files\\ACD System\\ACDSee\\8.0\\ACDSee8.exe";

 GetAllFileVersionInformation(ExeName.c_str(), Memo1->Lines); // wersja pliku ACDSee8.exe

 GetAllFileVersionInformation(NULL, ListBox1->Items); // wersja pliku z którego kod jest uruchamiany
}
//--------------------------------

Jak widać jeżeli przekażemy funkcji zamiast pełnej ścieżki do pliku wartość NULL to kod będzie starał się odczytać informacje o wersji pliku z którego jest uruchamiany.

Kolejny przykład to funkcja GetFileVersionInfo odczytująca tylko wybrane informacje o wersji pliku:
 
#include <stdio.h>
//--------------------------------

String GetFileVersionInfo(char *ModulePath, String KeyName)
{
 LPVOID lpStr1 = NULL, lpStr2 = NULL;
 WORD* wTmp;
 DWORD dwHandlev = NULL;
 UINT dwLength;
 char sFileName[1024] = {0};
 char sTmp[1024] = {0};
 String sInfo;
 LPVOID* pVersionInfo;

 if(ModulePath == NULL) GetModuleFileName(NULL, sFileName, 1024);
 else strcpy(sFileName, ModulePath);

 DWORD dwInfoSize = GetFileVersionInfoSize((char*)(LPCTSTR)sFileName, &dwHandlev);
 if(dwInfoSize)
 {
  pVersionInfo = new LPVOID[dwInfoSize];
  if(GetFileVersionInfo((char*)(LPCTSTR)sFileName, dwHandlev, dwInfoSize, pVersionInfo))
  {
   if(VerQueryValue(pVersionInfo, "\\VarFileInfo\\Translation", &lpStr1, &dwLength))
   {
    wTmp = (WORD*)lpStr1;
    sprintf(sTmp, ("\\StringFileInfo\\%04x%04x\\" + KeyName).c_str(), *wTmp, *(wTmp + 1));
    if(VerQueryValue(pVersionInfo, sTmp, &lpStr2, &dwLength)) sInfo = (LPSTR)lpStr2;
   }
  }
  delete[] pVersionInfo;
 }
 return sInfo;
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 String ExeName = "c:\\Program files\\ACD System\\ACDSee\\8.0\\ACDSee8.exe";

 Label1->Caption = "Wersja pliku: " + GetFileVersionInfo(ExeName.c_str(), "FileVersion");
 Label2->Caption = "Wersja pliku: " + GetFileVersionInfo(NULL, "FileVersion");
}
//--------------------------------

W przypadku tej funkcji trzeba jej przekazać jako argumenty nie tylko ścieżkę dostępu do pliku,  ale również nazwę klucza, który chcemy odczytać.

...powrót do menu. 

AntiKill - blokowanie możliwości zamknięcia procesu przez inny proces.

    AntiKill to sposób na zablokowanie możliwości zamknięcia (zabicia) jednego procesy przez drugi proces i o tym jest ta porada. Realizacja tego zadania wymaga stworzenia specjalnej funkcji, którą nazwałem BlockProcess. Funkcja pobiera tylko jeden argument jest to numer procesu (PID), który chcemy zabezpieczyć przed zamknięciem. W celu pobrania numeru PID tworzymy kolejną funkcję EnumProcess, która będzie wyliczała wszystkie aktywne procesy. Wyliczone procesy będą zapisywane na dwóch listach, pierwsza to obiekt ListBox1, na którym będą wyświetlane tylko nazwy tych procesów, drugi obiekt to lista ProcessPID typu TStringList i obiekt tej klasy trzeba zadeklarować w pliku nagłówkowym, w sekcji private lub public jako obiekt globalny. Obiekt ProcessPID będzie przechowywał zarówno nazwę jak i numer (PID) procesu. Zasada działania tych list jest tak, że wybieramy na liście ListBox1 jakiś proces, a następnie w zdarzeniu OnClick przycisku np. Button1, w którym następuje wywołanie funkcji BlockProcess jest porównywana nazwa procesu wybrana w ListBox1 z nazwą procesu na ProcessPID i na tej podstawie zwracany jest numer tegoż procesu z listy ProcessPID, no a potem ten numer jest przekazywany jako argument funkcji BlockProcess. Funkcja EnumProcess pobiera dwa argumenty, pierwszy to uchwyt do obiektu ListBox1, a konkretnie jego właściwości Items, drugi to uchwyt do listy ProcessPID.

Funkcja EnumProcess wymaga dołączenia do projektu biblioteki tlhelp32.h, natomiast funkcja BlockProcess wymaga dwóch bibliotek Aclapi.h i psapi.h, ale tutaj uwaga, jeżeli podczas kompilacji programu zostaną zgłoszone błędy o treści [C++ Error] Aclapi.h(103): E2015 Ambiguity between 'PACCESS_MASK' and 'Windows::PACCESS_MASK', itp... to te dwie biblioteki (tylko te dwie) należy umieścić w przestrzeni nazw o nazwie Windows. Obydwie funkcje definiujemy jeszcze przed konstruktorem klasy, tak by konstruktor mógł skorzystać z funkcji BlockProcess w celu zablokowania możliwości zamknięcia własnego procesu, nie jest to oczywiście konieczne, ale wskazane w przypadku takiego programu. Potrzebne będą jeszcze dwa przyciski, Button1 wywoła funkcje EnumProcess wyliczającą procesy a Button2 wywoła funkcje BlockProcess blokującą możliwość zamknięcia procesu wybranego na liście ListBox1, dlatego trzeba umieścić na formularzu również obiekt ListBox1.

/* PLIK NAGŁÓWKOWY */
private
:
        TStrings *ProcessPID;

 

/* PLIK ŹRÓDŁOWY */
#include "Unit1.h"
#include <tlhelp32.h>

namespace Windows
{
  #include "Aclapi.h"
  #include "psapi.h"
}

//---------------------------------------------------------------------------

DWORD BlockProcess(int pid)
{
 DWORD dwErr;
 HANDLE hpWriteDAC = OpenProcess(WRITE_DAC, false, pid);
 dwErr = GetLastError();

 SID security = {SID_REVISION, 1, SECURITY_WORLD_SID_AUTHORITY, 0};

 EXPLICIT_ACCESS ea =
 {
  PROCESS_TERMINATE, DENY_ACCESS, NO_INHERITANCE,
  {
   0, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, TRUSTEE_IS_USER,
   reinterpret_cast<char* >(&security)
  }
 };

 ACL* pdACL = 0;
 dwErr = SetEntriesInAcl(1, &ea, 0, &pdACL);

 dwErr = SetSecurityInfo(hpWriteDAC, SE_KERNEL_OBJECT,
            DACL_SECURITY_INFORMATION, 0, 0, pdACL, 0);


 LocalFree(pdACL);
 return dwErr;
}
//---------------------------------------------------------------------------
void EnumProcess(TStrings *List, TStrings *ListPID)
{
 ListPID->Clear();
 List->Clear();

 void *Snap;
 PROCESSENTRY32 proces;

 Snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS , 0);
 proces.dwSize = sizeof(PROCESSENTRY32);

 if(Process32First(Snap , &proces))
 {
  do
  {
   if(proces.szExeFile[0] != '[')
   {
    List->Add((String)proces.szExeFile);
    ListPID->Add((String)proces.szExeFile + "=" + (String)proces.th32ProcessID);
   }
  }
  while(Process32Next(Snap , &proces));
 }
 CloseHandle(Snap);
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) /* KONSTRUKTOR KLASY FORMULARZA */
        : TForm(Owner)
{
 BlockProcess(GetCurrentProcessId());
 ProcessPID = new TStringList;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender) /* WYLICZANIE PROCESÓW */
{
 EnumProcess(ListBox1->Items, ProcessPID);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender) /* ZABEZPIECZENIE PROCESU PRZED ZAMKNIĘCIEM */
{
 int PID = ProcessPID->Values[ListBox1->Items->Strings[ListBox1->ItemIndex]].ToIntDef(0);
 BlockProcess(PID);
}

Tyle w zasadzie wystarczy, dowolny proces raz zabezpieczony przed zamknięciem nie będzie mógł być zamknięty przez inny proces do czasu jego ponownego uruchomienia, czyli do momentu gdy sam siebie nie zamknie, lub do restartu systemu. Funkcja zabezpieczająca zabezpiecza proces przed wszystkimi programami (procesami) za wyjątkiem systemowego Menadżera zadań, który ma specjalne uprawnienia i nic nie jest w stanie zabezpieczyć procesu przed Menadżerem zadań.

Teraz dobrze byłoby sprawdzić, czy to działa. W tym celu tworzymy w naszym programie funkcje CloseProcess, która pobiera jako argument numer PID procesu, który ma zamknąć. Zasada pobrania numeru PID dla tej funkcji jest dokładnie taka sama jak dla funkcji BlockProcess. Funkcja zostanie wywołana w zdarzeniu OnCLick przycisku Button1.

/* PLIK ŹRÓDŁOWY */
#include "Unit1.h"
#include <tlhelp32.h>


namespace Windows
{
  #include "Aclapi.h"
  #include "psapi.h"

}
//---------------------------------------------------------------------------
bool CloseProcess(DWORD ID)
{
 DWORD ExitCode;
 HWND hWnd;
 bool Result = true;

 if(ID)
 {
  hWnd = OpenProcess(PROCESS_ALL_ACCESS, true, ID);
  if(hWnd)
  {
   GetExitCodeProcess(hWnd, &ExitCode);
   Result = TerminateProcess(hWnd, ExitCode);
  }
  else return false;
 }
 else return false;

 CloseHandle(hWnd);
 return Result;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button4Click(TObject *Sender)
{
 int PID = ProcessPID->Values[ListBox1->Items->Strings[ListBox1->ItemIndex]].ToIntDef(0);
 CloseProcess(PID);
 Button1Click(Sender); // odświeżenie listy po zamknięciu procesu
}

Teraz żeby sprawdzić, czy to działa, uruchamiamy nasz program i na razie nie zabezpieczamy nic przed zamknięciem, następnie uruchamiamy np. program Notatnik, następnie klikamy na Button1 i na ListBox1 pojawia się lista aktywnych procesów. Wybieramy z listy proces NOTEPAD.EXE, następnie klikamy na przycisku Button3 w celu zamknięcia procesu i proces oczywiście się zamyka. Lista się odświeży automatycznie, gdyż w zdarzeniu OnClick przycisku Button3 znajduje się odwołanie do zdarzenia OnClick przycisku Button1. Teraz ponownie uruchamiamy Notatnik, jego proces nie pojawi się automatycznie na ListBox1 więc klikamy ponownie na Button1, wybieramy z listy NOTEPAD.EXE i zabezpieczamy proces przed zamknięciem klikając na przycisku Button2, następnie klikamy na przycisku Button3, żeby zamknąć proces i proces nie zamyka się, gdyż jest zabezpieczony przed zamknięciem. Taki proces może być od teraz zamknięty przez samego siebie, przez Menadżera zadań, lub podczas zamykania systemu.

...powrót do menu. 

Pobieranie tytułu i uchwytu do okna lub obiektu w oknie wskazywanego przez kursor.

W celu pobrania uchwytu do okna lub obiektu znajdującego się w oknie na który wskazuje kursor, można posłużyć się funkcję WindowFromPoint, funkcja ta pobiera jako argument wskaźnik do struktury POINT zawierającej koordynaty wskazywanego punktu na pulpicie. Do pobrania koordynatów wskazywanych przez kursor można posłużyć się funkcją GetCursorPos lub obiektem klasy TMouse i jego funkcją CursorPos. Mając uchwyt do okna możemy do niego wysłać jakiś komunikat, lub np. odczytać tytuł okna. Ponieważ wskazując jakieś okno na pulpicie nie mamy możliwości wykonywania działań myszką w obrębie naszego programu, więc do pobrania uchwytu do okna wskazywanego przez kursor myszy i tytułu tego okna, można posłużyć się obiektem Timer umieszczając cały kod w jego zdarzeniu OnTimer. Należy przy tym pamiętać by ustawić właściwość Enabled tegoż obiektu na true, oraz żeby ustawić mu jakaś małą wartość w właściwości Interval, dobrze jest też ustawić właściwość FormStyle formularza na fsStayOnTop, po to by okno naszego programu zawsze pozostawało na wierzchu. Do pobrania tytułu okna można posłużyć się funkcją GetWindowText, która jako pierwszy argument pobiera uchwyt do okna, drugi argument to bufor w którym będzie przechowywany tytuł okna, a trzeci argument to rozmiar tego bufora. Funkcja GetWindowTextLength pobiera rozmiar tytułu okna.

//--------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
 POINT pCur;
 GetCursorPos(&pCur);
 HWND hWnd = WindowFromPoint(pCur);

 char *Buf = "";
 int l = GetWindowTextLength(hWnd) + 1;
 GetWindowText(hWnd, Buf, l);
 Edit1->Text = (String)Buf;
}
//--------------------------------

Lub:

//--------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
 HWND hWnd = WindowFromPoint(Mouse->CursorPos);

 char *Buf = "";
 int l = GetWindowTextLength(hWnd) + 1;
 GetWindowText(hWnd, Buf, l);
 Edit1->Text = (String)Buf;
}
//--------------------------------

Pojawia się tutaj jednak pewna niedogodność, otóż uchwyt jest pobierany do okna tylko wtedy gdy kursor wskazuje na belkę tytułową okna, lub bezpośrednio na pusty obszar okna, jeżeli jednak wskazuje na jakiś obiekt w oknie, to pobierany jest uchwyt do tego okna. Rozwiązaniem dla tego problemu jest funkcje GetAncestor, która pobiera uchwyt do przodka obiektu, jeżeli przekażemy tej funkcji jako pierwszy argument uchwyt do okna wskazywanego przez kursor, to funkcja ta zwróci uchwyt do przodka tego obiektu. Jako drugi argument trzeba przekazać funkcji jedną z trzech wartości:

W tej poradzie skorzystam z wartości GA_ROOTOWNER.

//--------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
 HWND hWnd = GetAncestor(WindowFromPoint(Mouse->CursorPos), GA_ROOTOWNER);

 char *Buf = "";
 int l = GetWindowTextLength(hWnd) + 1;
 GetWindowText(hWnd, Buf, l);
 Edit1->Text = (String)Buf;
}
//--------------------------------

Teraz pobierany jest tytuł okna na którym znajduje się obiekt. Jeżeli jednak kursor wskaże np. na ikony na pasku zadań, to nie zostanie pobrany tytuł tegoż obszaru paska, można to zmienić, ale nie spowoduje to wyświetlenia tytułu okna poprzez wskazanie jego ikony na pasku zadań, lecz tytułu obszaru paska zadań.

//--------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
 HWND hWnd = GetAncestor(WindowFromPoint(Mouse->CursorPos), GA_ROOTOWNER);

 char *Buf = "";
 int l = GetWindowTextLength(hWnd) + 1;
 GetWindowText(hWnd, Buf, l);

 if(l <= 1)
 {
  hWnd = WindowFromPoint(Mouse->CursorPos);
  l = GetWindowTextLength(hWnd) + 1;
  GetWindowText(hWnd, Buf, l);
 }

 Edit1->Text = (String)Buf;
}
//--------------------------------

...powrót do menu. 

Odczytywanie informacji o zasobach komputera za pomocą protokołu WMI.

WMI (ang. Windows Management Instrumentation) - jest to zestaw protokołów i rozszerzeń systemu Windows umożliwiających zarządzanie i dostęp do zasobów komputera, takich adaptery sieciowe, aktualnie otwarte programy, lista procesów, odczyty z wbudowanych czujników temperatury, odczytów woltomierzy itp.

Biblioteka Wbemidl.h udostępnia szereg funkcji umożliwiających pobranie informacji o podzespołach komputera, takich jak np. BIOS, dyski twarde, napędy CD/DVD, karty sieciowe, karty graficzne, karty dźwiękowe, itd. Opisanie wszystkich funkcji obsługujących protokół WMI zajęło by mi zbyt wiele czasu, dlatego przedstawiam gotową funkcję WMIInfo, która pobiera dwa argumenty, pierwszy to wskaźnik na klasę TStrings co umożliwia wyświetlenie wyników w obiektach typu Memo, RichEdit, ListBox, itp. Drugi argument pobierany przez funkcję to typ zasobu (klasy) o której chcemy uzyskać informację.

Stworzyłem dodatkową funkcję TranslateWMI, która nie jest potrzebna do działania funkcji WMIInfo, lecz może być pomocna. Funkcja tłumaczy informacje zwracane przez funkcję WMIInfo na język polski. Przedstawiona przeze mnie funkcja nie jest kompletna, zawiera tylko kilka tłumaczeń, więc można ją sobie uzupełnić według potrzeb. Jeżeli nie potrzebujesz tej funkcji, to musisz zmienić ten fragment kodu wewnątrz funkcji WMIInfo:

lpList->Add(TranslateWMI(String(wsName)) + ": " + lValue);

na taki kod:

lpList->Add(String(wsName) + ": " + lValue);

i to są wszystkie zmiany jakie trzeba w takim przypadku wprowadzić.

By można było w ogóle skorzystać z protokołu WMI, trzeba najpierw zarejestrować ochronę i ustawić domyślne wartości ochrony procesu aplikacji. Służą do tego funkcje CoInitialize i CoInitializeSecurity. W przykładzie funkcje zostały umieszczone w konstruktorze klasy. Przy zamykaniu programu trzeba wyrejestrować ochronę aplikacji za pomocą funkcji Couninitialize, najlepiej jest to zrobić w destruktorze klasy formularza, dlatego trzeba w sekcji public piku nagłówkowego zadeklarować destruktor klasy i zdefiniować go w pliku źródłowym.

W przykładzie znajduje się również tablica WMIResource zawierająca typy zasobów. Znajdują się tutaj również dwa zdarzenia OnClick. W zdarzeniu dla przycisku Button1 zostaną pobrane i wyświetlone w Memo1 informacje o BIOS'ie, a w zdarzeniu dla Button2 dwa zostaną wyświetlone informacje dla wszystkich zasobów.

Podczas pobierania informacji może nastąpić wstrzymanie programu na kilkadziesiąt sekund, ma to związek z tym, że niektóre zasoby wymagają dłuższego czasu, jak np. informacje o napędach CD/DVD, w tym konkretnym przypadku, jeżeli w napędzie znajduje się np. płyta z uszkodzonym bot sektorem, program może się zatrzymać nawet na kilka minut, gdyż funkcja będzie próbowała uzyskać dostęp do napędu, przy czym funkcja nie odczytuje informacji o nośniku danych (o płycie).

//--------------------------------
public:
       __fastcall TForm1(TComponent* Owner);
       __fastcall ~TForm1(); // dodajemy ten wpis


//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"

#include <Wbemidl.h> // wymagana biblioteka
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"


TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 CoInitialize(NULL);
 if(CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT,
    RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, 0) != S_OK)
 {
  ShowMessage("Błąd inicjowania interfejsu WMI");
  Button1->Enabled = false;
  Button2->Enabled = false;
 }
}
//---------------------------------------------------------------------------
AnsiString TranslateWMI(String text)
{
 AnsiString TableWMI[] =
 {
  "Caption", "Name", "Description", "Manufacturer", "DeviceID",
  "
Description", "SystemName", "SystemCreationClassName", "BIOSVersion", "Version",
  "
ReleaseDate"
 };

 AnsiString TableTranslate[] =
 {
  "Nagłówek", "Nazwa", "Typ urządzenia", "Producent", "Identyfikator wystąpienia urządzenia",
  "Opis", "Nazwa komputera", "Klasa systemu", "Wersja BIOS'u", "Wersja",
  "Wersja danych"

 };

 for(DWORD i = 0; i < ARRAYSIZE(TableWMI); i++)
 {
  if(SameText(text, TableWMI[i])) return TableTranslate[i];
 }
 return text;
}
//---------------------------------------------------------------------------
void WMIInfo(TStrings *lpList, WideString wsClass)
{
 IWbemLocator *pWbemLocator = NULL;
 if(CoCreateInstance(CLSID_WbemAdministrativeLocator, NULL,
     CLSCTX_INPROC_SERVER|CLSCTX_LOCAL_SERVER, IID_IUnknown, (void**)&pWbemLocator) == S_OK)
 {
  IWbemServices *pWbemServices = NULL;
  WideString wsNamespace = WideString("root\\cimv2");

  if(pWbemLocator->ConnectServer(wsNamespace, NULL, NULL, NULL, 0, NULL,
     NULL, &pWbemServices) == S_OK)
  {
   IEnumWbemClassObject *pEnumClassObject = NULL;
   WideString wsWQL = WideString("WQL"),
   wsQuery = WideString("Select * from ") + wsClass;

   if(pWbemServices->ExecQuery(wsWQL, wsQuery, WBEM_FLAG_RETURN_IMMEDIATELY,
      NULL, &pEnumClassObject) == S_OK)
   {
    IWbemClassObject *pClassObject = NULL;
    ULONG uCount = 1, uReturned;
    if(pEnumClassObject->Reset() == S_OK)
    {
     int iEnumIdx = 0;

     while(pEnumClassObject->Next(WBEM_INFINITE, uCount, &pClassObject, &uReturned) == S_OK)
     {
      lpList->Add("");

      SAFEARRAY *pvNames = NULL;

        if(pClassObject->GetNames(NULL, WBEM_FLAG_ALWAYS | WBEM_MASK_CONDITION_ORIGIN, NULL, &pvNames) == S_OK)
      {
       long vbl, vbu;

       SafeArrayGetLBound(pvNames, 1, &vbl);
       SafeArrayGetUBound(pvNames, 1, &vbu);

       for(long idx = vbl; idx <= vbu; idx++)
       {
        long aidx = idx;
        wchar_t *wsName = 0;
        VARIANT vValue;
        VariantInit(&vValue);
        SafeArrayGetElement(pvNames, &aidx, &wsName);

        BSTR bs = SysAllocString(wsName);
        HRESULT hRes = pClassObject->Get(bs, 0, &vValue, NULL, 0);
        SysFreeString(bs);

        if(hRes == S_OK)
        {
         AnsiString lValue;
         Variant vVar = *(Variant*)&vValue;
         if(vVar.IsArray())
         {
          for(int i = vVar.ArrayLowBound(); i <= vVar.ArrayHighBound(); i++)
          {
           Variant vTmp = vVar.GetElement(i);

           if(!lValue.IsEmpty()) lValue += ", ";

           lValue += VarToStr(vTmp);
          }
         }
         else
         {
          lValue = VarToStr(vVar);
         }
         if(!lValue.IsEmpty())
          lpList->Add(TranslateWMI(String(wsName)) + ": " + lValue); // tutaj podłączono tłumaczenie
        }
        VariantClear(&vValue);
        SysFreeString(wsName);
       }
      }
      if(pvNames) SafeArrayDestroy(pvNames);

      iEnumIdx++;
     }
    }
    if(pClassObject) pClassObject->Release();
   }
   if(pEnumClassObject) pEnumClassObject->Release();
  }
  if(pWbemServices) pWbemServices->Release();
 }
 if(pWbemLocator) pWbemLocator->Release();
}
//---------------------------------------------------------------------------
char *WMIResources[] =
{
 "Win32_1394Controller",
 "Win32_BaseBoard",
 "Win32_Battery",
 "Win32_BIOS",
 "Win32_Bus",
 "Win32_CacheMemory",
 "Win32_CurrentProbe",
 "Win32_DesktopMonitor",
 "Win32_DeviceMemoryAddress",
 "Win32_DiskDrive",
 "Win32_DisplayConfiguration",
 "Win32_DisplayControllerConfiguration",
 "Win32_DMAChannel",
 "Win32_Fan",
 "Win32_FloppyController",
 "Win32_FloppyDrive",
 "Win32_HeatPipe",
 "Win32_IDEController",
 "Win32_InfraredDevice",
 "Win32_IRQResource",
 "Win32_Keyboard",
 "Win32_MemoryArray",
 "Win32_MemoryDevice",
 "Win32_MotherboardDevice",
 "Win32_NetworkAdapter",
 "Win32_NetworkAdapterConfiguration",
 "Win32_OnBoardDevice",
 "Win32_ParallelPort",
 "Win32_PCMCIAController",
 "Win32_PhysicalMemory",
 "Win32_PhysicalMemoryArray",
 "Win32_PnPEntity",
 "Win32_PointingDevice",
 "Win32_PortableBattery",
 "Win32_PortConnector",
 "Win32_PortResource",
 "Win32_POTSModem",
 "Win32_PowerManagementEvent",
 "Win32_Printer",
 "Win32_PrinterConfiguration",
 "Win32_PrintJob",
 "Win32_Processor",
 "Win32_Refrigeration",
 "Win32_SerialPort",
 "Win32_SerialPortConfiguration",
 "Win32_SMBIOSMemory",
 "Win32_SoundDevice",
 "Win32_SystemEnclosure",
 "Win32_SystemMemoryResource",
 "Win32_SystemSlot",
 "Win32_TapeDrive",
 "Win32_TemperatureProbe",
 "Win32_UninterruptiblePowerSupply",
 "Win32_USBController",
 "Win32_VideoConfiguration",
 "Win32_VideoController",
 "Win32_VoltageProbe",
 "Win32_CDROMDrive",
 NULL
};
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 ::SetCursor(LoadCursor(NULL, IDC_WAIT));

 Button1->Enabled = false;
 Button2->Enabled = false;

 Memo1->Lines->Clear();
 Memo1->Lines->Add("• =============== [Win32_BIOS] =================");
 WMIInfo(Memo1->Lines, "Win32_BIOS");
 Memo1->Lines->Add("");

 ::SetCursor(LoadCursor(NULL, NULL));

 Button1->Enabled = true;
 Button2->Enabled = true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 ::SetCursor(LoadCursor(NULL, IDC_WAIT));

 Button1->Enabled = false;
 Button2->Enabled = false;

 Memo1->Lines->Clear();
 for(int i = 0; WMIResources[i]; i++)
 {
  Memo1->Lines->Add("• =============== [" + String(WMIResources[i]) + "] =================");
  WMIInfo(Memo1->Lines, WMIResources[i]);
  Memo1->Lines->Add("");
 }

 ::SetCursor(LoadCursor(NULL, NULL));

 Button1->Enabled = true;
 Button2->Enabled = true;
}
//---------------------------------------------------------------------------
__fastcall TForm1::~TForm1() // destruktor klasy formularza
{
 CoUninitialize();
}
//---------------------------------------------------------------------------

...powrót do menu.