Mis mälu mul masinas/serveris on

Aeg-ajalt tekib küsimus, et masinas on Windowsi (ja minu) teada miski hulk mälu, aga kui suured ja kui palju mooduleid masinas on ja kas midagi annab veel juurde panna, pole teada.  Või äkki tuleb hoopis olemasolevad moodulid välja vahetada?

Siin aitab vana hea WMI.  Nimelt annab selle käest küsida, et palju mälu kontroller üldse toetab ning palju emaplaadil pesasid on:

Get-WmiObject Win32_PhysicalMemoryArray

Siin tuleb muidugi teada, et atribuut MaxCapacity on kilobaitides. Seega võiks tulemust loetavuse huvides natuke parandada.

$MaxCapacity = @{name="Max memory (MB)"; expression={$_.MaxCapacity/1KB}}

Get-WmiObject Win32_PhysicalMemoryArray |
  Format-Table $MaxCapacity, MemoryDevices –AutoSize

Siit saime kätte maksimaalse mälu hulga ja pesade arvu, aga mis ühikutes mälu praegu masinas on?

Get-WmiObject Win32_PhysicalMemory

Jällegi oleks hea tulemus natuke loetavamaks teha:

$capacity = @{name="Capacity (MB)"; expression={$_.Capacity/1MB}}

Get-WmiObject Win32_PhysicalMemory |
  Format-Table DeviceLocator, $capacity, speed –AutoSize

Kõik ülaltoodu töötab lokaalses masinas, aga kui oleks vaja sama info saada kätte eemalt?  WMI liides on ka eemalt kättesaadav, vaja vaid läheneda kasutajana, kellel on lubatud WMI-liidese poole pöörduda:

$me = Get-Credential domain\kasutaja

Get-WmiObject Win32_PhysicalMemoryArray –ComputerName masin –Credential $me |
  Format-Table $MaxCapacity, MemoryDevices –AutoSize

Get-WmiObject Win32_PhysicalMemory –ComputerName masin –Credential $me |
  Format-Table DeviceLocator, $capacity, speed –AutoSize

Ainult et nüüd on meil probleem.  Iga Get-WmiObject käsk loob eemal olevasse masinasse ühenduse, saab info ja paneb ühenduse uuesti kinni.  Lisaks tuleb eraldi oodata iga masina taga, millega soovitakse ühendust saada.  Ja kui minu ning eemalasuva masina vahel on tulemüür, siis võib seal WMI jaoks vajalikud pordid kinni olla.  Ning nende lahtitegemine on natuke problemaatiline (WMI kasutab DCOM-liidest ehk dünaamilisi porte).

Antud situatsioonile on kaks võimalikku lahendust.  Esimeseks oleks kasutada Powershelli kaugühendusi.  Võrgutulemüürides on vaja avada vaid üks port (tcp/5985) ja sihtmasinas saaks ligipääsu lubada ka mitte-adminkontole:

#Requires -Version 2
$session = New-PSSession –Computername masin1, masin2 –Credential $me

Invoke-Command –Session $session { Get-WmiObject Win32_PhysicalMemoryArray} |
  Format-Table PSComputername, $MaxCapacity, MemoryDevices –AutoSize

Invoke-Command –Session $session { Get-WmiObject Win32_PhysicalMemory} |
  Format-Table PSComputername, DeviceLocator, $capacity, speed –AutoSize

Remove-PSSession $session

Windows 8/Server 2012 (ja värskemad) võimadavad aga veelgi lihtsamat lähenemist.  Nimelt on seal Powershelli sisse tehtud WMI alternatiivliides, mis samuti kasutab sessioone: CIM.  Sessioonid muide on samad, mis Powershelli kaugühenduse puhulgi.

#Requires -Version 3
#Requires -Modules CimCmdlets
$session = New-CimSession -ComputerName masin1, masin2 -Credential $me

Get-CimInstance Win32_PhysicalMemoryArray -CimSession $session |
  Select-Object  PsComputerName, $MaxCapacity, MemoryDevices |
  Format-Table –AutoSize

Get-CimInstance Win32_PhysicalMemory -CimSession $session |
  Sort-Object PsComputerName |
  Select-Object PsComputerName, DeviceLocator, $capacity, speed |
  Format-Table –AutoSize

Remove-CimSession $session

Üllatuste vältimiseks tasub veel ära mainida, et ülaltoodud tehnika abil saab kätte mitte ainult süsteemimälu, vaid võib leida ka muud, nagu näiteks videomälu jms.

Kasutajaprofiilid ja Powershell

Viimasel ajal olen korduvalt kokku puutunud vajadusega tegeleda lokaalsete kasutajaprofiilidega Windowsi masinates.  Tavaliselt pakuvad huvi kaks teemat:

  • oleks vaja kasutajaprofiil uue konto kätte anda
  • tahaks kustutada ülearused kasutajaprofiilid

Esimest tegevust on sageli vaja, kui toimub Active Directory migratsioon ja kasutajakontod tõstetakse uude AD domeeni.  Siis saab selle tegevuse läbi viia kasutades tarkvara Active Directory Migration Tookit, kasutades Security Translation Wizard’it.

Teise tegevusega on natuke raskem.  Üks viis antud juhul tegutseda on avada My Computer Properties |  Advanced System Settings | User Profiles | Settings .  Avaneb aken, kus on kirjas kõik kasutajaprofiilid lokaalses masinas, nende maht ja viimati kasutamise kuupäev.  Samas võtab mahu arvutamine arvestatava hulga aega, nii et selle järel tuleb (vahel päris kaua) oodata.  Ja lisaks ei saa seal nimekirja sorteerida (see on vaikimisi sorteeritud kasutajakonto nime järgi).  Kui tahaks nimekirja potensiaalselt kustutamisele minevatest profiilidest kiiremini või mingite tingimuste järgi, siis võiks ju kasutada käsurida ja Powershelli.

WMI andmebaas sisaldab nimekirja kasutajaprofiilidest.  Selleks tuleb välja kutsuda WMI klass Win32_UserProfile:

get-wmiobject Win32_UserProfile

Kuna WMI klassidel on alati küljes ülearused atribuudid, siis võiks ekraanile manada ainult olulise info:

Get-WmiObject Win32_UserProfile |
  Sort-Object LastUseTime |
  Format-Table LastUseTime, RoamingConfigured, LocalPath, SID -AutoSize

Tulemuses on näha kasutajaprofiilid sorteerituna viimati kasutatud kuupäeva järgi (vanemad enne) koos faktiga, et kas profiil on Roaming Profile. Tasub tähele panna, et atribuudi LastUseTime sisu võõraste kasutakontode kohta näeb ainult admin-õigustes kasutaja. Puuduseks on see, et atribuut LastUseTime on WMI kuupäeva vormingus, mida Powershell ei suuda ise normaalseks kuupäevaks teisendada.  Õnneks on selle peale mõeldud ning WMI objektid tagastatakse koos lisatud meetodiga, mis oskab kuupäeva konvertida:

$lastused = @{name="last used"; expression={$_.ConvertToDateTime($_.LastUseTime)}}
$roaming  = @{name="Roaming?";  expression={$_.RoamingConfigured}}

Get-WmiObject Win32_UserProfile |
  Sort-Object LastUseTime -Descending |
  Format-Table $lastused, $roaming, LocalPath, SID –AutoSize

Roaming Profile peaks olema muuhulgas salvestatud ka serverisse, nii et neid võib rahulikumalt kustutada.  Edasi oleks vaja valida välja profiilid, mida on viimati kasutatud varem, kui ette antud hulk päevi (näiteks 60) tagasi.  Lisaks võiks välja jätta süsteemsed profiilid ja need, mis parajasti kasutusel on:

Get-WmiObject Win32_UserProfile |
  Where-Object {-not ($_.Loaded -or $_.Special) } |
  Where-Object { $_.ConvertToDateTime($_.LastUseTime) -le (Get-Date).AddDays(-60) } |
  Sort-Object LastUseTime |
  Format-Table $lastused, $roaming, LocalPath, SID –AutoSize

Kui tekib tahtmine näha SID-i asemel kasutajakontot, siis saab seda teha teise WMI klassiga:

Get-WmiObject Win32_Account -Filter ("sid='{0}'" -f $mysid)

Nüüd jääb veel üle sõelale jäänud profiilid ära kustutada:

Get-WmiObject Win32_UserProfile |
  Where-Object {-not $_.Loaded } |
  Where-Object { $_.ConvertToDateTime($_.LastUseTime) -le (Get-Date).AddDays(-60) } |
  Remove-WmiObject

Mõistlik oleks antud tegevuste jada vormistada funktsiooniks või skriptiks, et saaks mugavasti ette anda päevade arvu ja veel mõned parameetrid. Kui tahad alltoodud koodi salvestada skriptiks, siis jäta ära esimene ja viimane rida.

function Get-UserProfile {
    Param (
    	[String[]] $ComputerName = ".",
    	[int] $Days,
    	[ScriptBlock] $Filter,
    	[Switch] $Delete
    )

    $userProfiles = Get-WmiObject Win32_UserProfile -ComputerName $ComputerName
    if ($PSBoundParameters.Keys -contains "Filter") {
    	$userProfiles = $userProfiles |
    		Where-Object $Filter
    }
    if ($PSBoundParameters.Keys -contains "Days") {
    	$userProfiles = $userProfiles |
    		Where-Object { $_.ConvertToDateTime($_.LastUseTime) -le (Get-Date).AddDays(0 - $Days) }
    }

    if ($PSBoundParameters.Keys -contains "Delete") {
      $userProfiles | Remove-WmiObject
    } else {
    	$lastused = @{name="Last used"; expression={$_.ConvertToDateTime($_.LastUseTime)}}
    	$roaming = @{name="Roaming ?"; expression={$_.RoamingConfigured}}
    	$machine = @{name="Computer"; expression={$_.__Server}}
        $uName = @{name="User Account"
                   expression={Get-WmiObject Win32_Account -Filter ("sid='{0}'" -f $_.sid) |
                                 Select-Object -ExpandProperty caption}
        }

    	$userProfiles |
      	    Sort-Object LastUseTime -Descending |
      	    Format-Table $lastused, $roaming, $machine, LocalPath, $uName, SID -AutoSize
    }
}

Kui ülaltoodud funktsioon on laetud Powershelli sessiooni, siis saab seda kasutada nii profiilide nimekirja saamiseks, kui ka kustutamiseks. Kui vormistasid ülaltoodud koodi skriptiks, siis pead hoolitsema, et see skript ka üles leitaks (ja et signeerimata skriptide kasutamine oleks lubatud). Alltoodud näidete eelduseks on, et on laetud funktsioon või skript on sama nimega nagu funktsioon ja asub kaustas, kust Powershell skripte/programme otsib ($env:Path).

# get all roaming user profiles, which are located on server
Get-UserProfile -Filter {$_.RoamingPath -like "\\server\profiles*"}

# get user profiles last used 30 (or more) days ago
Get-UserProfile -Days 30

# get user profiles from several computers
Get-UserProfile -ComputerName server1, server2 -Days 60

# delete corrupted user profiles
Get-UserProfile -Filter {$_.Status -eq 3} -Delete

Tasub tähele panna, et ülaltoodud WMI klass toimib alates Windows Vista-st. Varasemate OS-ide jaoks on vaja alternatiivseid lahendusi. Nagu näiteks Server 2003 Resource Kit’i utiliit DELPROF.  Selle utiliidi jaoks pole vaja ülaltoodud võimlemist, kuna see utiliit oskab ise vanu profiile üles otsida:

delprof /q /i /d:60

Ainuke probleem selle tööriistaga on, et see on ametlikult toetatud vaid Windows 2000/XP või Server 2003 peal.  Windows Vista/Server 2008 ja värskemad pole toetatud.  Kui soovid ilma Powershellita hakkama saada, siis tuleb vaadata 3. partei tööriistade poole, nagu näiteks DelProf2. Selle käsurida sobib algse utiliidiga.

On veel võimalus kasutada VBScriptis kirjutatud skripti, kuigi ka selle puhul on kurdetud, et see ei tööta Windows 7 peal.