Błąd „Root element is missing” przy wywołaniu Runbooka SMA

Najczęściej taki błąd pojawia się, gdy z poziomu bloku inline lub z funkcji wywoływany jest inny Runbook. Zachowanie takie jest niedozwolone.
Ostatnio spotkałem się z innym powodem tego błędu.
Normalnie w Powershellu możliwe jest indeksowanie tablicowe obiektu zwracanego z wyrażenia objętego nawiasami:

1
$var = (get-service)[0]

W przypadku użycia takiego wyrażenia w SMA zwracany jest właśnie błąd „Root element is missing”

Eksport/Import szablonów certyfikatów z AD w Windows 2012

Dzisiaj będzie parę słów o imporcie i eksporcie szablonów certyfikatów w Microsoft Active Directory.
Te dane są zapisane w partycji konfiguracji AD i przez to są współdzielone przez wszystkie domeny w obrębie lasu AD.
Czasem jednak zdarzają się sytuacje, kiedy zachodzi potrzeba przeniesienia tych danych między lasami, na przykład przy wdrażaniu testowanego uwcześnie rozwiązania w środowisku przed produkcyjnym do produkcyjnej domeny.
Zadanie takie może być wykonane na dwa różne sposoby. Pierwszy jest oczywisty dla tych, którzy mają z systemem windows duże doświadczenie, ale sposób ten jest brzydki i może powodować duże komplikacje w dzisiejszych systemach. Dlatego omówię go później.
Wraz z nadejściem systemu Windows 2008 R2 została wprowadzona nowa rola AD CS, która nazywa się AD CS Web Enrollment Service.
Służyć ona ma publikacji oraz użytkowaniu CA pomiędzy lasami AD, przy założeniu istnienia jedynie Zewnętrznej relacji zaufania pomiędzy tymi lasami. Więcej na ten temat można znaleźć tutaj.
Na tej samej stronie zostały też zamieszczone dwa świetne skrypty napisane w powershellu, które stanowią istotę dzisiejszego wpisu. Dzięki nim można wyeksportować oraz zaimportować szablony do oraz z plików XML w formacie MS-XCEP.
Niestety, te skrypty posiadają dwa błędy, które ujawniają się w trakcie pracy z systemem Windows 2012:

  1. skrypty zostały napisane jako zwykłe CMD-Let’y, które niestety nie są ładowane w W2k12. Szybkim obejściem tego problemu jest zakomentowanie deklaracji funkcji wraz z zamykającym nawiasem klamrowym na początku i końcu skryptu. Ponadto należy zakomentować deklarację parametrów jako cmd-let binding. Po tych zmianach skrypty uruchamiać się będą w normalny sposób z możliwością podania odpowiednich danych wejściowych, jak jest to wymagane.
  2. Funkcja eksportująca ma w niewłaściwy sposób zrealizowaną obsługę weryfikacji istnienia jednego z tablicowych atrybutów szablonu. Linię:
    1
    $superseded = if ($temp.Settings.SupersededTemplates -eq 0) {

    Należy zastąpić następującą:

    1
    $superseded = if ($temp.Settings.SupersededTemplates.Length -eq 0) {

    Dzięki temu zabiegowi zarówno eksport jak i import przebiegną pomyślnie..

  3. Ostatnią niedogodnością tych skryptów jest zależność od zewnętrznego modułu Power Shell. Ponieważ moduł jest napisany w .NET, na pewno istnieje możliwość wyeliminowania tej zależności, jednak ten temat będzie przedmiotem innego posta.

Wspominana wcześniej „brzydka metoda” to jest stara komenda ldifde wraz z wymaganymi flagami służącymi do eksportu:

1
ldifde -m -v -d cn=%Template1%,%LDAP% -f %Template1%.ldf

Najważniejszą częścią tej komendy jest przełącznik -m, który skutkuje wynikowym plikiem ldf pozbawionym odwołań do obiektów AD Forest.
Żeby zaimportować wygenerowany plik należy użyć bardzo podobnej komendy.:

1
 ldifde -i -k -f %Template1%.ldf

Metoda ta powinna działać dla wszystkich systemów, począwszy od Windows 2000.

Jak zdalnie wywołać opublikowane zadanie na kliencie SCCM 2012

Witajcie,

W sieci można znaleźć wiele pytań o zdalne wywoływanie akcji klienta SCCM poprzez skrypty.
W oficjalnym SDK do SCCM 2012 nie ma absolutnie nic na temat takiej możliwości. W wersji do SCCM 2007 jest przykład skryptu wykonywanego lokalnie, wywołującego obiekty klas COM CPAppletMgr oraz UIResourceMgr. Niestety, konfiguracja DCOM, która umożliwiałaby zdalne użycie tych obiektów jest conajmniej uciążliwa, a przy okazji bezużyteczna na dłuższą metę, gdyż niedorzecznym była by wiara, że klasy te (a w szczególności GUID’y) nie zmienią się w przyszłości.
Bardziej naturalną formą integracji z SCCM’em jest wykonywanie wywołań WMI.
I tutaj znowu Microsoft podłożył wszystkim autorom skryptów kukułcze jajo – w SDK do wersji 2012 jest jedynie suchy opis klas WMI dostępnych w kliencie i serwerze, jednakże brakuje jakichkolwiek wskazówek dotyczących ich używania.
Po dłuższych poszukiwaniach odnalazłem fantastyczne narzędzie: SMSCLICTR, które zbiera wszystkie możliwe ustawienia i wywołania w proste do użycia biblioteki .NET. Mogą być one wykorzystane także w powershell’u, jednakże często użycie zewnętrznych typów danych w PS jest zabronione przez polityki bezpieczeństwa systemu.
Ostatecznie nie pozostało nic innego, jak utworzenie własnego kawałka kodu, który będzie wykonywał wymagane akcje.

  1. Po pierwsze, konieczne jest wywołanie odświeżenia polityk klienta SCCM. Może to zostać wykonane przez poniższą funkcję:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function ForceMachinePolicyRefresh
    {
    param($clientname, $username, $userpass, $PolicyID)
    $ms = new-object system.management.managementscope
    $ms.Path = "\\$clientname\root\CCM"
    $ms.options.username = $username
    $ms.options.password = $userpass
    $mc = new-object system.management.managementclass($ms, 'SMS_Client', $null)
    $mc.invokeMethod("TriggerSchedule", $PolicyID)
    return $mc
    }

    Parę uwag do powyższego kodu:

    • Połączenie do przestrzeni nazw WMI następuje w formie bezpośredniej, bez użycia rzutowania, dostępnego w powershell na klasę [wmi], z powodu konieczności przekazania poświadczeń. Kod ten może wywoływać WMI zdalnie, dzięki zastosowaniu $ms.options.username które wspierane jest tylko w przypadku zdalnych połączeń.
    • Jest to ogólna funkcja umożliwiająca wymuszenie wywołania dowolnej harmonogramowej akcji. Tak więc może zostać użyta nie tylko do odświeżania polityki komputera, ale także do zastosowania ustawień konfiguracji lokalnej, co zostanie pokazane za moment.
    • Ostatnią rzeczą, dotyczącą tego małego kawałka kodu jest fakt, iż może zostać użyty jako szablon do manipulacji dowolnymi właściwościami i metodami opublikowanymi przez klasę WMI SMS_Client. Dopiero teraz SDK może się okazać pomocne.
  2. Tak więc mając zdefiniowaną tę procedurę pomocniczą możemy przejść do sedna sprawy. Poniższa funkcja służy do uruchomienia wymuszonej publikacji podanego w argumencie pakietu lub aplikacji. Funkcja pobiera wszystkie wymagane argumenty, tak że jest zupełnie niezależna od kontekstu, w którym jest wykonywana:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    function InvokeOptionalAdvertisement
    {
    Param($clientname, $username, $userpass, $advertID, $packID)
    $mc = ForceMachinePolicyRefresh -clientname $clientname -username $username -userpass $userpass -PolicyID "{00000000-0000-0000-0000-000000000021}"
    $ms = new-object system.management.managementscope
    $ms.path = "\\$clientname\root\ccm\policy\Machine\ActualConfig"
    $ms.options.username = $username
    $ms.options.userpass = $userpass
    $query =new-object System.Management.ObjectQuery
    $query.QueryString = "Select * From CCM_SoftwareDistribution where ADV_AdvertisementID = '$advertID' and PKG_PackageID = '$packID'"
    $searcher = new-object system.management.managementobjectsearcher($query)
    $searcher.Scope = $ms
    $advs = $searcher.Get
    $enum = $advs.GetEnumerator()
    $enum.MoveNext()
    $adv = $enum.Current
    $adv.SetPropertyValue("ADV_RepeatRunBehavior", "RerunAlways")
    $adv.SetPropertyValue("ADV_MandatoryAssignments", "True")
    $adv.Put()
    $query1 = new-object System.Management.ObjectQuery
    $query1.QueryString = "Select ScheduledMessageID FROM CCM_Scheduler_ScheduledMessageID like '" + $adv.ADV_AdvertisementID + "-" + $adv.PKG_PackageID + "%'"
    $searcher1 = new-object System.management.managementobjectsearcher($query1)
    $searcher1.scope = $ms
    $scheds = $searcher1.Get()
    $scheds | Foreach-Object { $mc[1].invokeMethod("TriggerSchedule", $_.ScheduledMessageID) }
    return $adv
    }

    No i oczywiście słowo komentarza do powyższego kodu:

    • Na początku wywołujemy odświeżenie polityki komputera, aby mieć pewność, że pracujemy na aktualnych danych publikacji pobranych z SCCMa. Tę akcję wykonujemy przy użyciu naszej pomocniczej procedury.
    • Następnie definiujemy nowy kontekst zarządzania WMI obejmujący aktualnie załadowaną konfigurację klienta SCCM.
    • Kolejno, tworzymy instację klasy ObjectQuery, która reprezentować będzie zapytanie WMI. To zapytanie pobiera z przestrzeni wszystkie obiekty klasy CCM_SoftwareDistributionktóre pasują do przekazanych wymagań.
    • Poprzez wywołanie metody $searcher.Get umieszczamy w zmiennej $advs wszystkie obiekty będące wynikiem zapytania.
    • Dzięki użyciu sztuczki z pobraniem enumeratora i jego właściwości $enum.Current otrzymujemy tylko pojedynczy obiekt, zamiast kolekcji zawierającej ten obiekt.
    • Teraz pora na wykonanie głównego zadania. Modyfikowane są dwie właściwości obranego obiektu, co powoduje, że zamiast opcjonalnej publikacji, na tym konkretnym kliencie staje się ona obowiązkowa i zostanie wykonana przy następnym wywołaniu harmonogramu tej publikacji.
    • Ostatnia część skryptu listuje wszystkie zarejestrowane w systemie obiekty harmonogramu i wymusza ich uruchomienie. To powoduje także wywołanie akcji harmonogramu dla naszej publikacji, którą chcemy wymusić.