środa, 17 sierpnia, 2011 - 9:34

hurtowa zmiana nazw grup

jak zmienić hurtem nazwy grup? aby to zrobić trzeba zastosować drobny trik.

było 1ooo razy i każdy powinien wiedzieć… ale odświeżyć sobie widomości raz na jakiś czas jest dobrze. trik polega na tym, że nie da się stricte zmienić nazwy grupy – można natomiast przenieść obiekt i podać nową nazwę. i tak jeśli wyświetli się ‘dsmod group /?’ to operacji zmiany nazwy nie ma. natomiast przy ‘dsmove /?’ można znaleźć taki przykład:


The user object for the user Jane Doe can be renamed to Jane Jones
with the following command:

    dsmove "cn=Jane Doe,ou=sales,dc=microsoft,dc=com" -newname "Jane Jones"

teraz wystarczy dodać prostą logikę i całość gotowa:

$searcher = New-Object DirectoryServices.DirectorySearcher([ADSI]“”)
$searcher.filter = objectClass=group
$searcher.SearchRoot=LDAP://cn=MyOU,cn=somewhere,dc=domain,dc=ad
$searcher.PropertiesToLoad.add(cn)
$searcher.PropertiesToLoad.add(distinguishedname)
$results=$searcher.FindAll()

foreach($r in $results) {
    $prop=$r.properties
    #here may be some logic. example - fix typo from gruop -> group
    if( $($prop.cn).contains(gruop) ) {
        dsmove $($prop.distinguishedname) -newname $($prop.cn).replace(gruop,group)
    }
}

eN.

piątek, 29 lipca, 2011 - 11:49

tablice wielowymiarowe (multidimensional arrays)

Autor: nExoR | Kategorie: script/developer

operacje na tablicach w powershell są dość dziwnie rozwiązane. jak zwykle przy walce z najprostszymi [wydawałoby się] operacjami jest całkiem sporo zabawy. dla tego ciekawostka: jak dodać element do tablicy wielowymiarowej?

krótki scenariusz: dostaję dwuelementowe tablice wyników. chcę przechowywać je w dwuwymiarowej tablicy. czemu nie hashtable? ponieważ nie ma indexów i nie da się sprawdzić np. “3 elementu tablicy”.

deklaracja tablicy jest trywialna:

  • $multiarray=@() – definicja pustej tablicy, uniwersalne
  • $multiarray=(“abc”,”def”),(“ghi”,”jkl”),(“mno”,”prst”) – definicja z 3ma elementami

najpierw zajmę się drugim przypadkiem, bo działa prawidłowo i jest poprawną tablicą wielowymiarową:

>echo $multiarray[0]

abc
def

>echo $multiarray[1][1]

jkl

no i supa. teraz spróbujemy dodać kolejną tablicę … i tu zaczyna się robić dziwnie:

>$multiarray+=(“uwx”,”yz”)
>echo $multiarray[3]

uwx

>echo $multiarray[3][1]

w

he? o co c’mon? o fakt, iż tablice można mieszać – dodawać elementy różnych typów. wykonując dodanie +=(“uwx”,”yz”) de facto dodajemy dwa kolejne elementy do tablicy, będące jednowymiarowymi tablicami [po prostu stringami]. jak zatem dodać kolejną tablicę? spróbujmy tak:

>$multiarray+=@(“uwx”,”yz”)

eeee. WRONG! nadal dokładnie tak samo. aby dodać tablicę trzeba użyć takiej zupełnie intuicyjnej składni z przecinkiem (; pokażę od początq:

>$multiarray=(“abc”,”def”),(“ghi”,”jkl”),(“mno”,”prst”)
>$multiarray+=,(“uwx”,”yz”)
>$multiarray[3]

uwx
yz

>$multiarray[3][1]

yz

w końcu dobrze! w takim razie całe rozwiązanie będzie wyglądało tak:

>$multiarray=@()
>TUTAJ JAKIŚ LOOP
       $multiarray+=,($cos1,$cos2)

pozostaje jeszcze kilka innych ciekawostek o tablicach wielowymiarowych, o których trzeba wiedzieć:

podana przeze mnie definicja to ‘tablica tablic’ a nie stricte ‘tablica wielowymiarowa’. poprawny zapis tablicy wielowymiarowej to:

$multiarray=New-Object ‘object[,]’ 10,2 – definicja tablicy wielowymiarowej 1ox2

jednak operacje na takiej tablicy są trudniejsze – np. dodanie dwóch elementów wymagało by wykonania dwóch operacji – dla [x,0] i [x,1]. przy ‘tablicy tablic’ wystarczy +=,($x,$y) a więc jest łatwiej. dostanie się do komórek jest wtedy $multiarray[x,y] a nie jak przy ‘tablicy tablic’ $multiarray[x][y].

pozostaje kwestia wyświetlenia na ekran i tutaj kolejna niemiła niespodzianka: wspaniała funkcja Format-Table nie potrafi poradzić sobie z takimi tablicami. czy będzie to prawdziwa wielowymiarowa czy tablica tablic – będą one wyświetlane jako kolejne elementy ): niestety pozostają stare niedobre sposoby iteracji i formatowanie pojedynczych rzędów ): i znów dla ‘tablicy tablic’ operacja enumeracji jest prostsza:

>foreach($ma in $multiarray) { echo "$($ma[0]) -> $($ma[1])" }

abc -> def
ghi -> jkl
mno -> prst
uwx -> yz

eN.

środa, 5 stycznia, 2011 - 16:37

wartościowy null – jak w Powershell sprawdzić limity skrzynek Exchange

Autor: nExoR | Kategorie: script/developer

Sprawa dość trywialna… przynajmniej tak by się można spodziewać. Limity wielkości skrzynek zapisane są w dwóch miejscach:

  • w parametrach samej skrzynki na Exchange – to dla tych, którzy będą mieli ‘use mailbox default’
  • w atrybutach w AD, które będą za chwilę opisane.

zacznę najpierw od szmacianego raportu SCCM:

nie wiem czy panowie z centrali sami przygotowywali ten raport czy to standardowy [domel, wiesz może?] ale jest to jeden z najbardziej oszukańczych raportów jaki widziałem:

  • dla osób z flagą ‘use default’ pokazuje wszędzie wartości null – jak by nikt nie miał skrzynki
  • dla osób, które mają zarówno ‘use default’ ale kiedykolwiek miały inne wartości, wartości te zachowane są w atrybutach AD [tyle, że nieużywane]. w zwiazq z tym SCCM pokazuje nieprawdziwe wartości, które są nieużywane.
  • jedyne co pokazuje dobrze to osoby, które mają coś ręcznie ustawione i jest to w użyciu.

masakra. trzeba było olać raport i zrobić to dobrze – czyli ręcznie. sprawa jest prosta. wykorzystywane są cztery atrybuty obiektu user [zapisuję małymi znakami, ponieważ przypominam, że PS wymaga używania w lowercase!]:

  • mdbusedefaults – czyli flaga ‘use defaults’. jeśli ustawiona jest na ‘true’ to reszta nie jest sprawdzana. po prostu dziedziczy parametry ustawione na storze [dla GT –> “na magazynie” (; ]
  • mdbstoragequota – wartość ‘issue warning’. quota
  • mdboverquotalimit – wartość ‘prohibit send’. softquota.
  • mdboverhardquotalimit – wartość ‘prohibit to send and receive’. hardquota.

image

so far, so good. problem zaczyna się kiedy próbuje się sprawdzić która skrzynka ma wartość domyślną, a która nie. powód – podstawowy dla PS czyli ‘co za cholerstwo zwraca ta funkcja?’. aby podjąć działanie zależnie od wartości mdbusedefaults trzeba sprawdzić czy ma wartość true czy false. a oto wyniki zabawy:

PS C:\> $r.properties.mdbusedefaults
False
PS C:\> if ($r.properties.mdbusedefaults -eq $false) {echo "aaaa"}
PS C:\> if ($r.properties.mdbusedefaults -eq $true) {echo "aaaa"}
PS C:\> if ($r.properties.mdbusedefaults -eq $null) {echo "aaaa"}
PS C:\> if ($r.properties.mdbusedefaults) {echo "aaaa"}
aaaa
PS C:\> if ($r.properties.mdbusedefaults -eq ‘False’) {echo "aaaa"}

co widać? w pierwszym zapytaniu dostajemy wartość false. kiedy jednak próbuje się tą wartość porównać z $false czy $true – żadna z powyższych. nie jest to również $null. w takim razie oczywistym wydaje się, że to musi być po prostu string. ale i to zawodzi.

pomimo usilnych prób nic z tego nie wyszło. poszedłem spać a na drugi dzień rozwiązanie jakoś tak samo przyszło:

PS C:\> $r.properties.mdbusedefaults.gettype()

IsPublic IsSerial Name                                     BaseType
——– ——– —-                                     ——–
True     False    ResultPropertyValueCollection            System.Collections.ReadOnlyCollectionBase

skoro jest kolekcją to trzeba to zrobić tak:

PS C:\> $r.properties.mdbusedefaults[0]
False
PS C:\> if ($r.properties.mdbusedefaults[0] -eq $false) {echo "aaaa"}
aaaa

pytanie ‘czemu, u licha, to jest kolekcją??!!’ pozostawiam otwarte. dla mnie to idiotyzm ale może ktoś zna jakieś logiczne wyjaśnienie?

a na zakończenie cały skrypt:

$searcher = New-Object DirectoryServices.DirectorySearcher([ADSI]“”)
$searcher.filter = (&(objectClass=user)(objectCategory=Person))
$searcher.SearchRoot=LDAP://OU=myou,DC=domain,DC=lab
$searcher.searchscope=Subtree
$searcher.PropertiesToLoad.add(mdbusedefaults)|out-null
$searcher.PropertiesToLoad.add(samaccountname)|out-null
$searcher.PropertiesToLoad.add(adspath)|out-null
$searcher.PropertiesToLoad.add(mdboverhardquotalimit)|out-null
$searcher.PropertiesToLoad.add(mdboverquotalimit)|out-null
$searcher.PropertiesToLoad.add(mdbstoragequota)|out-null
$results=$searcher.findall()

foreach($r in $results) {
   $prop=$r.properties
   if ($prop.mdbusedefaults) {
     if ( $prop.mdbusedefaults[0] -eq $true ) {
          echo $($prop.samaccountname) has default
  #if u want it clear out for SCCM reports
  #         $aduser=[ADSI]”$($prop.adspath)”
  #         $aduser.putex(1,”mdboverhardquotalimit”,”")
  #         $aduser.putex(1,”mdboverquotalimit”,”")
  #         $aduser.putex(1,”mdbstoragequota”,”")
  #        $aduser.SetInfo()
     } else {
          echo $($prop.samaccountname) limits: $($prop.mdboverhardquotalimit) $($prop.mdboverquotalimit) $($prop.mdbstoragequota)
     }
   } else {
      echo $($prop.samaccountname) has no mailbox
   }

}
echo done.

eN.

wtorek, 30 listopada, 2010 - 17:14

grep/find w powershell

Autor: nExoR | Kategorie: script/developer

o ile jestem gorącym zwolennikiem PS, o tyle w tych, wydawałoby się, najprostszych operacjach jest masakrycznie upierdliwy i nieintuicyjny. przykładem może być właśnie funkcjonalność aka ‘grep’ tego, co jest wypluwane. w przypadku PS są [co najmniej] dwie możliwości:

  • podstawowa to commandlet Select-String. no i fajno – wbudowany, grepuje, można sobie np. w pliq poszukać jakiegoś ciągu znaków. ponieważ jest ‘wbudowany’ pozwala na łatwe oskryptowanie… ale pojawia sie pewien problem: wszystko co zwraca PS jest obiektem… a nie ciągiem znaków. wynika z tego tyle, że kiedy się coś skryptuje, rzadko kiedy da sie go realnie użyć – a to z tego powodu, że trzeba wszystkie interesujące nas atrybuty obiektów przeformatować na stringi i porównywać jeden po drugim – bo zwykły pipe po prostu wrzuca obiekt i tyle. mówiąc dosadnie: pupa.
  • pozostaje ‘stary, dobry’ findstr, który w każdym Windows jest [no ok, są tacy co wycinają pinga z systemu twierdząc, że nie potrzebny i zmniejsza footprint, więc są pewnie i tacy, co nie mają findstr (; ]. “przepajpowanie” [LOL q: jak to po polskiemu? “przekazanie danych na standardowe wyjście”…] wyników na findstr automatycznie przekonwertuje cały output na stringi [huehue… “całe informacje wyjściowe na wartość tekstową” – ja bym chyba nie zrozumiał po polsq o co cho] a więc zadziała ładnie i zawsze. problem? jest zewnętrznym programem, przez co ciężej potem coś takimi wynikami zrobić. jednak przy standardowym wykorzystaniu przy pracy z PS jako shellem – na pewno będzie działał zawsze i niezawodnie… w przeciwieństwie do Select-String.

eN.

środa, 10 listopada, 2010 - 11:14

powershell – wywołanie funkcji

Autor: nExoR | Kategorie: script/developer

piszę za karę – wczoraj rzucałem klątwy więc dziś na tablicę “zapamiętam jak się wywołuje funkcje w PS”… ograniczę się jednego razu.

trywialna funkcja:

function fun {
    param ($a, $b)
    $a + $b
}

fun(1,2)

spodziewałem się na ekranie “3” a tymczasem dostałem 1<CRLF>2. $a okazało się być zwracane jako obiekt jakiś dziwny… a to dla tego że funkcje wywołuje się:

fun 1 2

jakoś tak nieintuicyjnie. de facto jest to to samo co fun (2) (3) więc podając fun(1,2) jest to wywołanie z pierwszym parametrem “1,2” i drugim null.

i pomimo,że podobne funkcje widziałem na wielu stronach nie mogło to do mnie dotrzeć. to jak w tym doświadczeniu, w którym trzeba policzyć ile było podań piłki, ew. w historii o Inkach, którzy nie widzieli zbliżających się łodzi…

eN.

wtorek, 19 października, 2010 - 9:53

SCCM Raport Lista modeli komputerów

W SCCM 2007 R2 brakuje raportu który by wyświetlił listę komputerów z nazwą producenta, typem modelu komputera, numerem seryjnym i nazwą usera.

Poniżej raport który robi:

1) tworzymy nowy raport

2) jako Report SQL Statement wklejamy:

SELECT
Distinct
SYS.Netbios_Name0,
SYS.User_Name0,
MOD.Manufacturer0,
MOD.Model0,
BIO.SerialNumber0
FROM v_R_System SYS
JOIN v_GS_COMPUTER_SYSTEM MOD on SYS.ResourceID = MOD.ResourceID
JOIN v_GS_PC_BIOS BIO on SYS.ResourceID = BIO.ResourceID
JOIN v_FullCollectionMembership FCM ON SYS.ResourceID = FCM.ResourceID
WHERE
FCM.CollectionID=@ID
ORDER BY SYS.Netbios_Name0

3) Tworzymy nowego Promta o nazwie ID, zaznaczamy Provide SQL Statement

4) Do Prompt SQL Statement wklejamy:

BEGIN
IF (@__filterwildcard = '')
SELECT DISTINCT CollectionID, Name FROM v_Collection ORDER BY Name
ELSE
SELECT DISTINCT CollectionID, Name FROM v_Collection
WHERE CollectionID like @__filterwildcard
ORDER BY Name
END

Proste i przydatne :-)

poniedziałek, 11 października, 2010 - 12:26

powershell – znaki diakrytyczne i import csv

Autor: nExoR | Kategorie: script/developer

dwie ciekawostki przy jednym prostym teście…

kiedy importuje się plik CSV z pojedynczym wierszem (oczywiście + nagłówek) atrybut ‘legth’ (count) zwraca null… przy każdej innej wielkości normalnie zwracana jest prawidłowa liczba. rzadko kiedy importuje się csv z jednym wierszem, ale przy testach nagle skrypt zaczął zwracać dziwne rzeczy…

plik, o którym mowa, zawierał znaki diakrytyczne – tego właśnie dotyczyły testy: funkcji sczytującej csv i konwertującej wartości na ograniczony alfabet. tutaj wyszła kolejna ciekawostka – z jakiegoś powodu większość znaków nie była konwertowana. powodem okazał się sposób zapisu pliq – PS czyta pliki zapisane w UTF-16 lub UTF-8. jeśli csv jest w ANSI to PS dostaje ‘sieczkę’ i nie za bardzo rozpoznaje w nim znaki alfabetu (; zabawne, bo get-content radzi sobie z tym znakomicie. najprostszy sposób konwersji wygląda zatem: get-content .\myfile.csv>>myfile2.csv bo zapis jest standardowo w unicodzie.

eN.

piątek, 8 października, 2010 - 16:54

funkcje w powershell

Autor: nExoR | Kategorie: script/developer

funkcje w powershell to bardzo dziwny i nieintuicyjny wynalazek. należy więc intuicję zmienić, a to oznacza – zrozumieć jak ten diabeł działa. pełny art w wer. angielskiej polecam przeczytać, ponieważ to w pełni wyjaśnia problem. ja przedstawię go po swojemu – czyli w skrócie i na przykładach q:

weźmy prostą funkcję testową:

function fun {
 param ($str)
 echo będe liczył
 $str+=aaaa
 return $str
}
$a=coś
$a+=fun(!!!)
echo $a

znając trochę języki programowania można się spodziewać, że na ekranie pojawi się:

będę liczył

coś!!!aaaa

tymczasem realny output będzie zaskaqjący:

cośbęde liczył!!!aaaa

o co cho? cała funkcja konwertowana jest w locie na obiekt. wszystko, co ‘wychodzi’ z funkcji traktowane jest jako atrybut obiektu. w związq z tym:

  • return nie jest potrzebny – kończy wykonanie funkcji, ale zwykły output wystarczy
  • każdy output jest jednym z atrybutów obiektu zwracanego przez funkcję

przy tej samej funkcji można zrobić inne doświadczenie:

$a=fun(!!!)
$a
$a.length
$a[1]

które pokaże, że zwracana jest dwuelementowa tablica zawierająca “będę liczył” oraz “!!!aaaa”.

funkcja w najprostszej postaci może wyglądać zatem tak:

function fun {
 param ($str)
 aaaa+$str
}

jeśli zatem w funkcji wykorzystywana jest jakaś funkcja zwracająca cokolwiek na ekran, niezbędne jest dodanie “|out-null” żeby nie otrzymać tego jako atrybut w zwracanym obiekcie.

eN.

wtorek, 28 września, 2010 - 12:37

gdzie jest moja przestrzeń?

Autor: nExoR | Kategorie: curiosity, script/developer, server

ciekawostka przyrodnicza: na serwerze kończy się miejsce na dysq. sprawdzam wielkość dysq – 33GB, sprawdzam zajętość katalogów przy pomocy explorera – wychodzi koło 16GB. ilość wolnego miejsca na dysq – bliska 0. o co c’mon?

jeszcze ciekawiej: dokładnie taka sama sytuacja jest na drugim dysq w tym serwerze. wychodzi na to, że zniknęło ok 4o%-5o% wszystkich dysqw – nie wiadomo gdzie i jak.

w pierwszej kolejności zadzwoniłem i zamówiłem nowe dyski do serwera. w między czasie trzeba sprawdzić o-co-cho.

dochodzenie

zacząłem od dość brzydkiej operacji – nadawania uprawnień do ‘zakazanych’ katalogów – system volume information, recycle bin i wszelkie inne systemówki, do których nie ma uprawnień a więc na pewno zostały wkalqlowane w sumaryczną wielkość. wszystkie miały pomijalną wielkość. to, czego się przy okazji dowiedziałem to to, że system sobie potem ładnie te katalogi ‘reperuje’ przywracając im po jakimś czasie [restart?] uprawnienia. ufff… przynajmniej sie nie rozjechał (;

kolejny strzał – shadow copy lub restore points. w w2k8 (R2) nie ma dostępnej opcji/zakładki Restore Points. próbowałem na siłę sprawdzić korzystając z rozwiązania z http://www.win2008workstation.com/forum/viewtopic.php?p=4301#p4301. zakładka pojawia się co prawda – ale wywala się z błędem. niemniej można to sprawdzić nieco bardziej ‘legalnie’:

ponieważ restore point jest ‘migawką’ shadow copy – powinien być zapis o niej w systemie. w związku z tym powinno dać się to sprawdzić przy pomocy vssadmin

PS C:\> vssadmin.exe list shadows
vssadmin 1.1 - Volume Shadow Copy Service administrative command-line tool
(C) Copyright 2001-2005 Microsoft Corp.

No items found that satisfy the query.

pupa. nie muszę w związq z tym dodawać, że sama funkcjonalność previous versions jest wyłączona. zaczynam zabawę z alternatywnym badaniem wielkości katalogów – czyli powershell. znalazłem fajny art krok-po-kroq wyjaśniający jak do tego podjeść. nie jest miłe i tak proste, jak bym się tego spodziewał – ale działa (: no i kilka fajnych tipsów PS przyda się na przyszłość:

"{0:N2}" -f ((Get-ChildItem ‘.\program files (x86)’ -recurse | Measure-Object -Property length -sum).sum / 1GB ) +" GB"

nie bez powodu pokazuję od razu ten katalog – ponieważ to właśnie on okazał się być złodziejem przestrzeni. jeszcze troszkę bardziej rozbudowana składnia do reqrencyjnego odpytania i jest winny!

PS C:\Program Files (x86)> get-childitem | %{"$_ size: "+ "{0:N2}" -f ((Get-ChildItem $_ -recurse | Measure-Object -Property length -sum).sum / 1GB )+" GB"}
[…]
Microsoft Configuration Manager size: 18,32 GB
[…]

explorer pokazuje dla tego katalogu znaczną różnicę w wielkości: 0B (słownie – zero bajtów). na drugim dysq sprawca jest ten sam – repozytorium SCCM i katalog SMSPKG.

dyski właśnie przyszły – a więc czas na kolejne hardtesty – jak przerzucić cały system na nowe dyski. spróbuję windows backup…

**UPDATED**

pozostaje pytanie – czemu explorer nie pokazuje wielkości niektórych katalogów. i tutaj odpowiedzią jest moja najulubieńsza, taka jej mać, funkcja – UAC. ma to związek ze starszym wpisem. ktoś mógłby stwierdzić, że to nie wina UAC tylko explorera – którego nie da się [normalnie] odpalić z tokenem administratora. powershell potrafił zliczyć wielkości, ponieważ był właśnie z nim uruchomiony. prawie na pewno – gdybym zamiast explodera używał jakiegoś menadżera plików firmy trzeciej – również zliczyłby katalogi prawidło. imho jednak takie rzeczy stanowią integralną część systemu i powinny być przemyślane w całości – jako spójna platforma. no nic – po prostu dodaję nową zasadę: “nie używaj explorera na serwerze z UAC”.

druga kwestia – jeszcze do zbadania – to z jakiej racji SCCM tak pożarł miejsce.

eN.

poniedziałek, 2 sierpnia, 2010 - 15:51

znaleźć obiekt po mailu

Autor: nExoR | Kategorie: script/developer

kilqkrotnie miałem problem – przychodzi ktoś i żąda żeby zrobić coś z kontem, którego email to …. chodzi o to, że czasem był to email, czasem alias – generalnie nie ma standardowego searcha, który potrafi to zrobić. otóż i skrypcik:

if($args.count -ne 1) {
    echo podaj parametr
    return
}

$searcher = New-Object DirectoryServices.DirectorySearcher([ADSI]“”)

$searcher.filter = (|(objectClass=user)(objectClass=group))
$searcher.searchscope=Subtree
$searcher.PropertiesToLoad.add(proxyaddresses)
$searcher.PropertiesToLoad.add(mailnickname)
$searcher.PropertiesToLoad.add(mail)
$searcher.PropertiesToLoad.add(samaccountname)
$results=$searcher.findall()

foreach($r in $results) {
    $prop=$r.properties
    if($prop.proxyaddresses) {
        $pa= ([string]($prop.proxyaddresses)).tolower()
        if($pa.contains($args[0]) ) {
            echo $($prop.samaccountname) -> $($prop.proxyaddresses)
        }
    }
    if($prop.mail) {
        $pa= ([string]($prop.mail)).tolower()
        if($pa.contains($args[0]) ) {
            echo $($prop.samaccountname) -> $($prop.mail)
        }
    }
    if($prop.mailnickname) {
        $pa= ([string]($prop.mailnickname)).tolower()
        if($pa.contains($args[0]) ) {
            echo $($prop.samaccountname) -> $($prop.mailnickname)
        }
    }
}
echo done.

pozostaje jeszcze jeden problem… foldery publiczne, które też mogą mieć email…

eN.

piątek, 23 lipca, 2010 - 12:27

Backup ISA TMG

ISA i TMG to takie fajne serwerki, które łatwiej jest postawić od początku niż przywrócić z kopii zapasowej. A jeszcze prościej zrobić jeden obraz z systemu, a następnie odtworzyć z obrazu, i wgrać najnowszy konfig. Ale jak więc zabezpieczać konfig ISA/TMG? Dość prosto i szybko można to zrobić skryptem, pamiętając o kilku rzeczach:

  • Backup konfiguracji powinien być trzymany na oddzielnym serwerze, najlepiej backupowanym przez coś innego
  • Pojedyńcza kopa zapasowa ma 7-8 MB, co przy kopii codziennej daje 3G na rok, a tygodniowej 420MB
  • Folder docelowy dobrze jest skompresować, wybierając odpowiednią flagę NTFS-ową na folderze, wtedy ilość miejsca z 7MB spada do 2.5MB, przy czym kolejne backupy będą zabierały jeszcze mniej
  • Jeśli certyfikaty będą eksportowane, to znajdą się również w konfiguracji TMG
  • Przy tmg, routing jest trzymany w konfiguracji TMG, więc przed importowaniem trzeba go wyrzucić z pliku netsh

A poniżej skrypt:

‘bAckupisa/tmgtofile
Const destination = “C:\bck\”
Const passwd = “12345678″

Const fpcExportImportPasswords = &H00000001
Const fpcExportImportUserPermissions =&H00000002
Const fpcExportImportServerSpecific= &H00000004
Const fpcExportImportEnterpriseSpecific = &H00000008
      fpcOptionalData = fpcExportImportPasswords Or _
      fpcExportImportUserPermissions Or _
      fpcExportImportServerSpecific Or _
      fpcExportImportEnterpriseSpecific
date = replace(Date,“/”,“_”)
Set root = CreateObject(“FPC.Root”)
Set firewall = root.GetContainingArray
firewall.ExportToFile destination & date & “_ISA_CONFIG.XML”_
                     ,fpcOptionalData,passwd

Set wshShell = WScript.CreateObject(“WScript.shell”)
wshshell.run “cmd/c “” netsh int ip dump > ” & _
              destination & date & “ip.nsh”"”
wshshell.run “cmd/c “” netsh int ip show dns > ” & _
             destination & date & “dns.txt”"”

wtorek, 13 lipca, 2010 - 13:59

pedanteria niepożądana

Autor: nExoR | Kategorie: script/developer

prosty search po obiektach w AD, które mają włączoną flagę niewygasania hasła:

$searcher = New-Object DirectoryServices.DirectorySearcher([ADSI]LDAP://OU=PL,OU=Country,DC=domain,DC=test)
$searcher.filter = (&(objectCategory=User)(userAccountControl:1.2.840.113556.1.4.803:=65536))
$searcher.propertiesToLoad.add(sAMAccountName)
$searcher.propertiesToLoad.add(displayname)
$searcher.propertiesToLoad.add(sn)
$searcher.propertiesToLoad.add(distinguishedname)
$s=$searcher.findall()
echo *************************

foreach($a in $s) {
    $i=$a.Properties
    echo [$($i.sAMAccountname)] , $($i.displayName) , $($i.sn), $($i.distinguishedName)
}

i okazuje się, ze na ekranie pustka… wyświetlają się tylko pola ‘sn’. WTF?

problemem jest pedanteria i używanie małych/wielkich znaków – okazuje się, że PS przyjmuje wyłącznie małe literki:

echo "[$($i.samaccountname)] , $($i.displayname) , $($i.sn), $($i.distinguishedname)"

pffff…

eN.

poniedziałek, 12 lipca, 2010 - 21:13

BCD i skrypty

Autor: domel | Kategorie: article, script/developer, windows

Totalny w-file tym razem. Tyle, że rozwiązany :)

Problem – zautomatyzować operację dodawania wpisów w BCD, żeby skryptem dodawać Pingwina do boot menu w Windows 7.

Pierwszy krok prosty: dodajemy wpis dla grub4dos - http://grub4dos.sourceforge.net/wiki/index.php/Grub4dos_tutorial#Booting_GRUB_for_DOS_via_the_Windows_Vista_boot_manager

Teraz wypada by to przenieść na inną maszynę – chwilka przekopywania się przez dokumentację i okazuje się, że BCDEdit umożliwia import i export ustawień. piknie – robić backup i potem go odzyskać na innej maszynie. Niestety nie działa. Z tego prostego powodu, że wpis dla W7 nie ma GUID {current}, tylko jakiś dziwoląg – niestety GUIDy są generowane pseudolosowo. Efekt: pingwin się podnosi, Windows nie ^^

Rozwiązania są dwa:

1. Wykorzystać WMI i BCDProvider (http://msdn.microsoft.com/en-us/library/aa362675(v=VS.85).aspx) i można zrobić tak (wymaga wcześniejszego dodania wpisów):

set args = WScript.Arguments
num = args.Count

if num <> 1 then
  WScript.Echo Usage: CScript \\nologo [Menu Selection]
  WScript.Quit 1
end if

MenuPick=args.Item(0)

Const BcdLibraryString_Description = &h12000004
Const BootMgrId = {9dea862c-5cdd-4e70-acc1-f32b344d4795}
Const DefaultType = &h23000003
Const WindowsImages = &h10200003

strComputer = .
Set objStoreClass = GetObject(winmgmts:{(Backup,Restore)}\\ & _
strComputer & \root\wmi:BcdStore)
objStoreClass.OpenStore “”, objStore
objStore.EnumerateObjects WindowsImages, colObjects
For Each objObject in colObjects
  objObject.GetElement BcdLibraryString_Description, objElement
  If Instr(objElement.String, MenuPick) Then
    objStore.OpenObject BootMgrId, objBootMgr
    objBootMgr.SetObjectElement DefaultType, objObject.ID
  End If
Next

2. wykręcić pętlę w CMD:

for /f tokens=3 %%a in (’bcdedit -create -d PJWSTK Linux  -application bootsector’) do set guid=%%a
bcdedit /set %guid% device boot
bcdedit /set %guid% path \grldr.mbr
bcdedit /displayorder %guid% /addlast

działa pięknie – po prostu pobiera trzecie słowo z outputu BCDEDIT – to ZAWSZE jest GUID :)

teraz można to wrzucić jako cmd /c “%scriptroot%\bcd.cmd” do Task Sequence w MDT i mamy zautomatyzowane dodawanie pingwinów na W7 :D

PS. warto pamiętać, ze sysprep kasuje zawartość BCD :)

piątek, 2 lipca, 2010 - 16:49

ping to file

Autor: nExoR | Kategorie: script/developer

taka mała piątkowa gimnastyka.

krótki kod, powalający sprawdzić czy host żyje i zapisać wyniki do pliq.

Get-Content .\hostyerr.txt | %{ $out=`ping $(($_).trim()) -n 1` ; if( ($out[5]).contains("0% loss")) {echo $_}} >hostsok

nie jest to rozwiązanie idealne, ale co fajnego:

  • dla zmiennej można przypisać wynik działania programu – używa sie tych ciapek w lewym-górnym rogu [jak się nazywają?]
  • znów wszystko można zrobić w jednej linijce.

zamiast ‘contains’ lepiej używać –match ale to kiedyś indziej bo…

czas na weekend.

eN.

wtorek, 25 maja, 2010 - 17:50

wsh.run

Autor: nExoR | Kategorie: script/developer, tips'n'tricks

prosty scenariusz: skrypt logowania, uruchamiający bginfo. podstawowy problem w vbs: obsługa katalogów ze spacją. niestety przekazanie “c:\program files\systeinternals\bginfo.exe” z podobnie wyglądającymi parametrami jest upierdliwe – ile tych cholernych cudzysłowów jest potrzebne?

WShell.run """"&SUPPORT_DIR&"bginfo.exe"" """&SUPPORT_DIR&"wrkstations.bgi"" /accepteula /timer:0"

a cały skrypcik wygląda tak:

********************************************************************************  prepare server environment - processexplorer and bginfo on servers         **                                                                             **    author:         nexorek[at]gmail.com                                     **                             last change  25.o5.2kd                          **                                                                             ********************************************************************************

OPTION EXPLICIT
ON ERROR RESUME NEXT
CONST SERVER_DIR=\\FILESERVER\gpofiles$\
Dim SUPPORT_DIR
Dim FSO, file
Dim WShell

********************************************************************************                                 MAIN                                        ********************************************************************************
set FSO=CreateObject(Scripting.FileSystemObject)
Set WShell = WScript.CreateObject(WScript.Shell)
SUPPORT_DIR=WShell.ExpandEnvironmentStrings(%programfiles%)&\sysinternals\
check admin directory
if not FSO.FolderExists( SUPPORT_DIR ) then
  FSO.CreateFolder( SUPPORT_DIR )
end if
copy if not exist
iFCopy(bginfo.exe)
iFCopy(wrkstations.bgi)
iFCopy(procexp.exe)
WShell.run “”&SUPPORT_DIR&bginfo.exe”" “”&SUPPORT_DIR&wrkstations.bgi”" /accepteula /timer:0

********************************************************************************                              SUBS’N'FUNCS                                   ********************************************************************************
Sub iFCopy(sourceFile)
  if not FSO.FileExists( SUPPORT_DIR&sourceFile ) then
    FSO.copyFile (SERVER_DIR&sourceFile), SUPPORT_DIR, true
  else
   check version
    if strcomp( FSO.getFileVersion(SERVER_DIR&sourceFile),FSO.getFileVersion(SUPPORT_DIR&sourceFile) )<>0 then
      FSO.copyFile (SERVER_DIR&source), SUPPORT_DIR, true
    end if
  end if
End Sub

eN.

czwartek, 25 marca, 2010 - 17:57

mapdrive script:automatyzacja mapowania dysków z serwera plików

prosta idea [nie zawsze wykonalna] – wedle podręcznikowego przydzielania uprawnień i zarządzania grupami polega na tym, że dla każdego udziału na FS tworzona jest grupa security-domain local – np. Share_FS01_RW_public. tej grupie nadawane są uprawnienia [w tym przypadq RW] a całe zarządzanie przydzielaniem uprawnień polega na dodaniu grupy funkcyjnej do grupy dostępowej. przykład w praktyce:

  1. zakładam w AD jednostkę organizacyjną OU=AccessGroups
  2. zakładam w AD jednostkę organizacyjną OU=Accounting
  3. zakładam grupę funkcyjną security-global ‘Accounting’ i dodaję odpowiednich userów). te 3 kroki oczywiście definiują miniaturkę podstawowego środowiska lab
  4. księgowość musi mieć swój prywatny katalog na FS więc:
    1. zakładam grupę Security-Domain Local o nazwie “AG_FS01_RW_Accounting” w OU-AccessGroups – to przykładowa notacja która pozwala w łatwy sposób odróżnić grupy dostępowe od innych, zawiera nazwę serwera, którego dotyczy, uprawnienia [dzięki temu można łatwo odróżniać grupy RW od R] oraz jakiego udziału dotyczą.
    2. zakładam katalog ‘Accounting’ na FS01 i publiqję go jako ‘\\FS01\Accouting’
    3. jedyne uprawnienia jakie zakładam na katalogu to “authenticated users:M” na poziomie udziału oraz “AG_FS01_RW_Accounting:RW” na poziomie NTFS
    4. teraz aby nadać uprawnienia do katalogu wystarczy, że dodam grupę ‘Accounting’ do ‘AG_FS01_RW_Accounting’

wadą takiego rozwiązania jest niezliczona ilość grup w złożonym środowisq – a więc czasem podręcznikowe rozwiązanie nie może być zastosowane. zalet jednak jest bardzo dużo:

  • wszystko zarządzane z jednego miejsca za pomocą ADUaC bez potrzeby logowania/sprawdzania na serwerach plików
  • ..czyli centralizacja zarządzania uprawnieniami
  • łatwość delegacji zarządzania uprawnieniami
  • utrzymywanie spójnej, prostej struktury: przejrzystość
  • prostota automatyzacji mapowania: ujednolicone/uniwersalne skrypty mapowania
  • automatyczne mapowanie dysków dla użytkowników zaraz po dodaniu do grupy funkcyjnej

poniżej zamieszczam przykładowy skrypt logowania mapujący dyski, który ma możliwość mapowania na podstawie przynależności do grupy. ponieważ użytkownicy nie należą bezpośrednio do grupy dostępowej, sprawdzany jest drugi poziom zagnieżdżenia – atrybut memberof. jest możliwość włączenia rekursywnego sprawdzania zagnieżdżenia ale ma to kilka mankamentów:

  • jest dość powolne w realnym środowisq, gdzie grup jest sporo
  • istnieje niebezpieczeństwo przypadkowego zmapowania katalogu z powodu powiązań pomiędzy grupami

******************************************************************************** map network drives lib for logon script                                     **   can be used to map multiple drives. possible error handling               **   user must have proper permissions - providing credentials not handled     **                    nexorek(at)gmail.com           25.iii.2kd                **EXAMPLE USE:                                                                 **addMapping “m”,”\\192.168.1.100\c$”                                          **addMapping “n”,”\\192.168.1.100\d$”                                          **mapNetworkDrives                                                             **                                                                             **EXECUTE WITH ERROR HANDLING:                                                 **addMapping “m”,”\\192.168.1.100\c$”                                          **addMapping “n”,”\\192.168.1.100\d$”                                          **if mapNetworkDrives<>0 then                                                  ** wscript.echo “do something with “&mapNetworkDrives                          **else                                                                         ** wscript.echo “netdrives mapped successfully”                                **end if                                                                       **                                                                             **MAP USER-named DRIVE                                                         **addMapping “h”,”\\server\share\”&logonName                                   **                                                                             **CONDITIONAL MAP DRIVE ON USER MEMBERSHIP                                     **if isMember(”group name”) then addMapping “s”,”\\server\sharename”           **                                                                             ********************************************************************************
ON ERROR RESUME NEXT
Const FileServer=\\put.servername.here

Dim dictMappings
Dim oADSystemInfo
Dim oUser, groupList, loginName, dictGroups, g
dim tempname

********************************************************************************                                 MAIN                                        ********************************************************************************used for store information about mapping drives
set dictMappings=CreateObject(Scripting.Dictionary)

Object for user/computer information
set oADSystemInfo=CreateObject(ADSystemInfo)
USER
set oUser=GetObject(LDAP://&oADSystemInfo.UserName)
loginName=oUser.SAMAccountName
USER GROUPS MEMBERSHIP
set dictGroups=CreateObject(Scripting.Dictionary)
groupList=oUser.getEx(memberOf)
for each g in groupList
  if not strcomp(lcase( left( getObject(LDAP://&g).objectCategory,8) ),cn=group ) then
    enumGroupsInGroup(LDAP://&g)
  end if
  tempname=lcase(mid(g,4,InStr(g,,)-4))
  if not dictGroups.exists(tempname) then dictGroups.add tempname, g
next

PUT MAPPINGS HERE. example:
if isMember(AG_FS01_RW_Accounting) then addMapping i,\\FS01\Accounting
mapNetworkDrives

********************************************************************************                              SUBS’N'FUNCS                                   ********************************************************************************
Sub addMapping(drvLetter, srvPath)
  dictMappings.add ucase(drvLetter)&:, srvPath
End Sub

Function mapNetworkDrives()
  ON ERROR RESUME NEXT
  Dim wshNetwork
  Dim AllDrives, i

  Set WshNetwork = WScript.CreateObject(WScript.Network)
  Set AllDrives = WshNetwork.EnumNetworkDrives()

  For each i in dictMappings
    unmap before mapping - just to be sure that drive letter is not used
     if dictMappings.Exists(i) then
       WShNetwork.RemoveNetworkDrive AllDrives.Item(i)
     end if
    WShNetwork.MapNetworkDrive i, dictMappings(i)
    mapNetworkDrives=cstr(hex(err.number))
  next
End Function

Function isMember(gName)
  if dictGroups.Exists(lcase(gName)) then
    isMember=TRUE
  else
    isMember=FALSE
  end if
End Function

Sub enumGroupsInGroup(gname)
  ON ERROR RESUME NEXT
  Dim grpList, gentry

  grpList=getObject(gname).getEx(memberOf)
  for each gentry in grpList
    if not strcomp(lcase( left( getObject(LDAP://&gentry).objectCategory,8) ),cn=group ) then
      dictGroups.add lcase(mid(gentry,4,InStr(gentry,,)-4)), gentry
      next line is recursion for multilevel nesting. for performance purposes assumed 2-level structure
      so it is disabled. there should be no more then second-level nesting for permissions
      in fact it can be even dengerous - someone may have accidently mapped drive  
      enumGroupsInGroup(”LDAP://”&gentry)
    end if
  next

End Sub

jak się okazuje, czasem teoria przekłada się na praktykę – mówię tu o podręcznikowym projektowaniu grup (global/domain local i standardowe AGDLP).

eN.

niedziela, 28 lutego, 2010 - 11:29

Ciężkie życie programisty ….

Autor: wAsyL | Kategorie: joke, script/developer, technology

Żart branżowy, na deszczową niedzielę {przynajmniej u mnie :-/}

Ciężkie życie programisty

czwartek, 11 lutego, 2010 - 13:19

Usuwanie starych kont komputerów z Active Directory

Kiedys do wywalania starych i nieużywanych kont komputerów z AD służył mi taki znaleziony w sieci skrypt:

 

dsquery computer -inactive 16 -limit 0 | dsrm -c -noprompt

 

ale coś przestał działać na Win2008.

Nie wnikając w szczegóły i chcąc iść  z duchem czasu przerobiłem go na PowerShella

 

Get-QADComputer -IncludedProperties pwdLastSet -SizeLimit 0 |where {$_.pwdLastSet -le (Get-Date).AddDays(-180) } | Remove-QADObject -DeleteTree  -Force

 

Może komuś się przyda przy porządkach w AD.

wtorek, 9 lutego, 2010 - 17:54

PS: jak usunąć elementy z listy oddzielanej średnikami

Autor: nExoR | Kategorie: script/developer

tak na prawdę to kolejna lekcja zabawy hashtablami i ciągami. scenariusz w moim przypadq był taki, żeby zweryfikować jakie konta należą do grupy lokalnych adminów na stacjach roboczych. skrypt logowania wrzucił zawartość grupy do bazy danych, potem pobrałem sobie dane do zmiennej $DataSet.Tables[0] . teraz trzeba wygenerować raport “wypisz wszystkie komputery, na których ktoś należy do adminów a nie powinien, z informacją jakie to konto”. rozwiązanie jest takie:

#connection objects
$sqlconnection=New-Object system.data.sqlclient.sqlconnection
$sqlcmd= New-Object System.Data.SqlClient.SqlCommand
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$DataSet = New-Object System.Data.DataSet

#connect MYDATABASE on MYSQLSERVER
$sqlconnection.ConnectionString=Server=MYSQLSERVER;database=MYDATABASE;Integrated Security=True
$sqlcmd.CommandText=select * from TABLENAME
$sqlcmd.Connection=$sqlconnection
$SqlAdapter.SelectCommand = $SqlCmd
#that is strange way of getting results - they are popullated into $dataset array. 
$SqlAdapter.Fill($DataSet)|out-null

#now when we have everything in an array so we can manipulate on data
#first define hashtable for computers with admin accounts
$unsecure=@{};
foreach ($row in $DataSet.Tables[0]) {
    #how to remove correct group/user names from check? one can try to 
    #operate on strings but it’s so… bueee
    #it’s easier and nicer to have an object for comparison. hashtable is perfect
    $splitrow=@{};

    #column 1 in my database contains memberof information separated with semicolon
    #f.ex. ‘domain admins;administrator;non-admin-user;’
    #so change this string into a hashtable:
    $row[1].split(;)|%{$splitrow.add($_,0)}
    #the second parameter is obligatory to fill but tottaly unimportant in this situation. 
    #so i put ‘0′ but could be anything.
    #now it’s easy to remove correct entries which are not interesting for this task
    $splitrow.Remove(administrator);
    $splitrow.Remove(Domain Admins);
    $splitrow.Remove(grupa_operatorow);
    $splitrow.Remove(specjalna_grupa);
    #as there is ‘;’ on the end it will always produce one additional, empty record. 
    $splitrow.Remove(“”);

    #if there are any unwanted logins, the number will be greater then 0
    if ($splitrow.count -gt 0) {
        #add entry to final table. i want to have it back as a string so -join is going to be helpful
        $unsecure.add($row[0],$splitrow.keys -join ;)
    } else {
        #out of interest - just informational
        echo KOMPUTER: $($row[0]) jest ok;
    }

} 

#now print information to a file
$unsecure.count|out-file c:\temp\admins.txt
$unsecure.getEnumerator()|Sort-Object name|out-file c:\temp\admins.txt -append

czego można się nauczyć z tego skryptu?

  • jak połączyć się z bazą
  • jak pobrać i operować na wynikach
  • jak radzić sobie z hashtablami
    • szczególnie ciekawy jest sposób sortowania – gdzie użyty jest getEnumerator. zwykłe $unsecure|Sort-Object nie zahula

sposób być może nie jest optymalny więc będę wdzięczny za sugestie i alternatywne rozwiązania (:

eN.

poniedziałek, 8 lutego, 2010 - 17:03

Dictionary object dla powershell czyli Hashtable

Autor: nExoR | Kategorie: script/developer

w PS zamiast obiektu słownikowego jest hashtable. Po przejrzeniu wielu wygooglanych przykładów oczywiście nie znalazłem odpowiedzi na moje pytania:

  • co jeśli wartością ma być zmienna tablicowa?
  • jak dostać się do takiej wartości?
  • jak zrobić enumerację z możliwością wykonania działań?

odpowiedzi są dość intuicyjne. nie ma problemu, żeby do hashtabla dodać tablicę jako wartość, a następnie robić jakieś operacje. enumeracja jest dokładnie taka sama jak w VBS:

#declaration
$hashtable=@{ aaa=@(0,0); bbb=@(0,0) }
#print out single value
$hashtable[aaa][1]++;
echo $hashtable[aaa][1]; #out -> 1
#enum
$hashtable
#enum with some operation
$hashtable[aaa]=@(5,10)
$hashtable[bbb]=@(3,1)
foreach($entry in $hashtable.keys) {
    echo $($hashtable[$entry][0]/$hashtable[$entry][1]*100)%;
} # out -> 300% 50%

póki co nie bawiłem się $hashtable.getEnumerator() – być może da się zoptymalizować to trochę.. ale działa wszystko bez problemu niemal tak samo jak VBSowy Dictionary (: listy są w .keys i .values, testy są metodami .containsKey i .containsValue, dodawanie .add, usuwanie .remove – różnice w wykorzystaniu są niewielkie (:

eN.