Wednesday, February 16, 2011

Get-DriveSpace

So, I'm not going through this right now, but here is the way to discover disk space utilization AND include mount points on remote machines.  It uses are fancy RegEx replaces to compair Win32_Volume.DeviceID and Win32_MountPoint.Volume to find the mount points associated to each disk.
$sessions = "localhost","PHSCONSOLE" | New-PSSession
$block = {
    gwmi -query "select * from Win32_Volume where DriveType='3'" | Select `
        @{Name="Server";Expression={$ENV:COMPUTERNAME}},`
        @{Name="Device";Expression={$_.DriveLetter}},`
        @{Name="MountPoint";Expression={$DID=$_.DeviceID;gwmi Win32_MountPoint | ? { (($_.Volume.Split("=")[1] -replace "[^a-z0-9-]|Volume","") -match ($DID -replace "[^a-z0-9-]|Volume","")) } | % { $_.Directory.Split("=")[1] -replace "`"","" }}},`
        @{Name="Capacity";Expression={[math]::round(($($_.Capacity)/1GB),2)}},`
        @{Name="FreeSpace";Expression={[math]::round(($($_.FreeSpace)/1GB),2)}},`
        @{Name="UsedSpace";Expression={[math]::round((($_.Capacity - $_.FreeSpace)/1GB),2)}},`
        @{Name="PercentFree";Expression={[math]::round(($($_.FreeSpace)/$($_.Capacity)*100),2)}}
}
Invoke-Command
-ScriptBlock $block -Session $sessions | Select Server,Device,MountPoint,Capacity,FreeSpace,UsedSpace,PercentFree | Sort Server,Device
Get-PSSession
| Remove-PSSession

Monday, January 24, 2011

Dump-DNS

"Red Rover, Red Rover, Send Server1 right over!"

Due to poor DNS management, how many have felt as if they were playing this childrens game when trying to find a single device on the network?  And, all because "that one guy" didn't believe in deleting old static DNS records when a machine was renamed or decomissioned.

It's easy enough to clear out...as long as you have permissions in DNS.  What if you don't, and what if you want to be pro-active?  PowerShell can help your line hold tight as the DNS bully runs into your line.

Lets help him become part of our team, and put him to use with this script:

CODE

#Dump-DNS
#Get the current domain name
$domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name
#Create the query string.
$query = "Select IPAddress,OwnerName from MicrosoftDNS_AType WHERE DomainName='$($domain)'"
#Retrieve the DNS data desired
$data = gwmi -namespace "root\MicrosoftDNS" -query $query -ComputerName $domain | Select IPAddress,OwnerName | ? { $_.OwnerName -ne $domain }

$hash = @{}
#Find all duplicated IPs
$data | % {$hash[$_.IPAddress] = $hash[$_.IPAddress] + 1 }
$ips = $hash.GetEnumerator() | ? { $_.value -gt 1 } | Select -Expand Name
$hash.Clear()
#Find all duplicated names
$data | % {$hash[$_.OwnerName] = $hash[$_.OwnerName] + 1 }
$machines = $hash.GetEnumerator() | ? { $_.value -gt 1 } | Select -Expand Name

#Display the data
$data | Select IPAddress,OwnerName,@{Name="Unique";Expression={($ips -notcontains $_.IPAddress -and $machines -notcontains $_.OwnerName)}} | Sort IPAddress

 code: copy : expand : collapse

EXPLANATION

Many people have been using the [System.Net.DNS] class to gather DNS information.  However, this does not always give the most accurate information, and you must request the information using a known IP or Name.  MS, in their infinite wisdom, provided the MicrosoftDNS WMI classes to pull directly from a MS DNS server the full list of DNS information.

Pulling all A Records from a DNS will gather a HUGE list of data.  It will retrieve every single resource record the server knows about.  So, whats the big deal?  Every record that any machine on the network has ever requested, both private and public.  Meaning, every query out to a website that has embeded content from any number of other sites - those are all recorded in the local DNS server.  We don't want those million records, only the private addresses.

To do this, we have to know what the current domain name is.  *Knock*Knock* ".Net, do you know how to do that?"  "Sure, use [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()"

Hence:
$domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name
Domain name: Check.

Now, it's time for a query:
$query = "Select IPAddress,OwnerName from MicrosoftDNS_AType WHERE DomainName='$($domain)'"

Simply use the domain name gathered with standard WQL to ... Select the IPAddress and OwnerName properties from the MicrosoftDNS_AType class where the DomainName property is equal to our local domain.  Easy, right?

Domain name: Check.
Query: Check.

Because the MicrosoftDNS classes are not a default WMI set of classes, we must connect into them by specifying the -namespace for PoSh to know where to point.  That would be "root\MicrosoftDNS". 

Using WQL, even though we've specified in our $query to only retrieve IPAddress and OwerName, will return a lot of WMI class information and connection information.  We don't care about that, so lets again select only IPAddress and OwnerName.  Much better.

"But, there is now a false positive for my domain name", you say.  Well, lets not select that either by filtering out selecting only entries where the OwnerName property is not equal to your domain name.

$data = gwmi -namespace "root\MicrosoftDNS" -query $query -ComputerName $domain | Select IPAddress,OwnerName | ? { $_.OwnerName -ne $domain }

Domain name: Check.
Query: Check.
Data: Check.

So, there you have it.  All the DNS A Records in your domain.  Now you just have to export to a CSV, create a new colum, build reports to show duplicate IP's and machine names merge the report with the CSV to show all the duplicates...  Oh, you don't want to do all that extra work?  Ok.  Let's bring PoSh back.

How do we find duplicate entries only?  We'll just use Sort-Object's -duplicate switch or Get-Duplicate.  Wait.  I forgot.  Those don't exist.  Well, we'll have to do it the hard way.

Create a hash table, parse through the $data object already created, and count up the value for each element.  Then, spit out the Name property for any item with a Value over 1.  Sounds easy - looks messy.

The main reason is that a has value must be enumerated in order to select the Name of an entry.  Other wise you would get a list of numbers (values) associated to the entries (names).

Yup.  This has to be done two times:  One time for IPAddress and one time for OwnerName.  With a $hash.Clear() in the middle to do just that.  Clear the entries.

$hash = @{}
#Find all duplicated IPs
$data | % {$hash[$_.IPAddress] = $hash[$_.IPAddress] + 1 }
$ips = $hash.GetEnumerator() | ? { $_.value -gt 1 } | Select -Expand Name
$hash.Clear()
#Find all duplicated names
$data | % {$hash[$_.OwnerName] = $hash[$_.OwnerName] + 1 }
$machines = $hash.GetEnumerator() | ? { $_.value -gt 1 } | Select -Expand Name

Domain Name: Check.
Query: Check.
Data: Check.
Duplicates: Check.
Output: Almost.

The next piece is very straight forward - although not by first appearance.

Here we're going to use Calculated Properties to display a status of True or False based on the $ips and $machines objects created previously.  Calculated Properties are very easy to work with.  They are simply hash tables (notice they are surrounded by @{}) with a Name and Expression of your choosing.  Expression can be any valid PoSh code and does take data from the pipe.

$data | Select IPAddress,OwnerName,@{Name="Unique";Expression={($ips -notcontains $_.IPAddress -and $machines -notcontains $_.OwnerName)}} | Sort IPAddress

Domain Name: Check.
Query: Check.
Data: Check.
Duplicates: Check.
Output: Check.

Done!

Remember.  The key to mastering PowerShell is not in your infalable ability to use Get-Help on every cmdlet over and over, or crutching on Step-Into.  It's research and knowledge of .NET and WMI.  The two greatest pieces of the PoSh puzzle.  Learn them well and you'll keep adding more members to your line.

"Red Rover, Red Rover, send...anything right over!"

Tuesday, May 18, 2010

2010SG Entries

So, I was going to post all of my entries in the 2010 Scripting Games here, but just lost the time.  For a good reason tho.  Our youngest daughter recently made her entrance into this world from the dark world of her mommy's tummy.  Now that she's here, I have more important things to do.

Still, I didn't want to leave anyone flat from not giving my entries.  So, his is the link to all of my entries:

http://2010sg.poshcode.org/code/search.xhtml?q=cruisader03

Happy coding every one, and I'll be blogging again in a few weeks!...ish.

Sunday, May 2, 2010

Get-CPUInfo?

Beginner Event 5

<#
    .SYNOPSIS
        Gathers cpu information from a computer.
    .DESCRIPTION
        Get-CPUInfo.ps1 gathers the cpu information via WMI on a give computer.  This can be run against a remote computer or on the local session using PSSessions.
    .PARAMETER computers
        Remote computers to create query.  Seperate with a comma (,) for multiple computers.
    .PARAMETER file
        File with a list of computers to query.
    .PARAMETER cred
        Run under specified credentials.  The user will be prompted to enter a username and password for script execution.
    .PARAMETER help
        Display help information.
    .INPUTS
        None. Piped objects are not accepted.
    .OUTPUTS
        Outputs to the screen or .Net 3.0 or higher GridView.
    .EXAMPLE
        C:\PS> .\Get-CPUInfo.ps1
        Gathers cpu information from the local computer.
    .EXAMPLE
        C:\PS> .\Get-CPUInfo.ps1 -computers "Code1","Code2","Code3"
        Gathers cpu information from each computer listed in -computers.
    .EXAMPLE
        C:\PS> .\Get-CPUInfo.ps1 -file list.txt
        Gathers cpu information from each computer contained in list.txt and displays them in the .Net 3.0 or higher GridView.
    .NOTES
        Name:       Get-CPUInfo.ps1
        Author:     Jes Brouillette (ThePosher)
        Last Edit:  05/02/2010 19:45 CST
#>
param (
    #Computer(s) to query
    #Seperate with a comma (,) for multiple computers
    [array]$computers,

    #File with a list of computers to query
    [string]$file,

    #Run under specified credentials
    [switch]$cred,     

    #Gather all CPU information
    [switch]$full,
   
    #Gather more detailed information, but not full
    [switch]$detailed,
   
    #Display in Grid View
    [switch]$grid
)

#create an array list of all computers being queried.
$list = @()
if ($computers) { $list = $computers }
elseif ($file) { $list = gc $file }
else { $list += "localhost" }

#A bug within Test-Connection will return $false when testing the local computer as "." as the response comes from "localhost"
#Replacing "." with "localhost" to allow validation to correctly function
$list = $list | % { $_.Replace(".","localhost") }

#Create sessions on all computers (remote or local)
$sessions = $list | ? { Test-Connection $_ -quiet -Count 1 } | % {
   
    #Gather credentials and create connections if -cred was specified
    if ($cred) { New-PSSession -ComputerName $_ -Credential (Get-Credential) }
   
    #Otherwise, just create connections
    else { New-PSSession -ComputerName $_ }
}

if ($sessions) {
    $command = { gwmi win32_processor }
   
    if ($full) { $selection = "*" ; $exclude = "PSComputerName","RunspaceId","PSShowComputerName","_*","CreationClassName"}
    elseif ($detailed) { $selection = "SystemName","MaxClockSpeed","Description","Name","Manufacturer" }
    else { $selection = "SystemName","MaxClockSpeed" }
   
    #Start the execution of all tasks simultaneously
    #Note again that $sessions contains the credentials, therefore they are not explicitely required for Invoke-Command
    if ($grid) { Invoke-Command -Session $sessions -ScriptBlock $command -ErrorAction SilentlyContinue | select $selection -ExcludeProperty $exclude | Out-GridView }
    else { Invoke-Command -Session $sessions -ScriptBlock $command -ErrorAction SilentlyContinue | select $selection -ExcludeProperty $exclude }
   
    $sessions | Remove-PSSession
}
else { Write-Host "No sessions available.  Please check the computers names you would like to query and try again." }

Get-VidMem

My 5-Star entry to gather video memory and report back on Win7 Aero capability (on memory amount alone) in the 2010 Scripting Games

http://2010sg.poshcode.org/code/502.xhtml


<#
    .SYNOPSIS
        Gathers video memory from a computer.
    .DESCRIPTION
        Get-VidMem.ps1 gathers the memory amounts of all video cards present on a give computer.  This can be run against a remote computer or on the local session.
    .PARAMETER computers
        Remote computers to create the variable in.  Seperate with a comma (,) for multiple computers.
    .PARAMETER file
        File with a list of computers to create the variable in.
    .PARAMETER logpath
        Alternate storage location for the log file.
    .PARAMETER logfile
        Alternate name for the log file.  Get-VidMem_Results.csv will be used if not specified.
    .PARAMETER append
        Append the log file if it exists.
    .PARAMETER cred
        Run under specified credentials.  The user will be prompted to enter a username and password for script execution.
    .PARAMETER quiet
        Run silently.
    .PARAMETER help
        Display help information.
    .INPUTS
        None. Piped objects are not accepted.
    .OUTPUTS
        Set-EnvVar_log.csv is created within the same directory as the script.
    .EXAMPLE
        C:\PS> .\Get-VidMem.ps1
        Gathers video card information from the local computer.  Output is displayed on screen and logged in Get-VidMem_Results.csv.
    .EXAMPLE
        C:\PS> .\Get-VidMem.ps1 -computers "Code1","Code2","Code3" -quiet
        Gathers video card information from each computer listed in -computers.  Output is only sent to Get-VidMem_Results.csv.
    .EXAMPLE
        C:\PS> .\Get-VidMem.ps1 -file list.txt -logpath "C:\LOGS" -logfile "Get-VideoMemory_All.csv"
        Gathers video card information from each computer contained in list.txt and saves the log file as C:\LOGS\Get-VideoMemory_All.csv"
    .NOTES
        Name:       Get-VidMem.ps1
        Author:     Jes Brouillette (ThePosher)
        Last Edit:  04/30/2010 00:38 CST
#>
param (
    #Computer(s) to create the variable in.  Seperate with a comma (,) for multiple computers
    [array]$computers,

    #File with a list of computers to create the variable in
    [string]$file,

    #Path to store the log file
    [string]$logPath,

    #Alternate name for the log file
    [string]$logFile = "Get-VidMem_Results.csv",

    #Append the log file
    [switch]$append,       

    #Run under specified credentials
    [switch]$cred,     

    #Runs silently and only generates a log file
    [switch]$quiet     
)

#Report function for all server not online
function Report-NotOnline {
    <#
        .SYNOPSIS
            Generate and object containing the computer name, Online status as $false, and the current date/time
        .PARAMETER computername
            String value of the computer to report
        .INPUT
            Accepts one string value
    #>
    param([string]$computer)
   
    #Although the only properties being reported back are Computer Online and Date, Select after the pipe will create the remaining properties as $null
    New-Object PSObject -Property @{
        Computer = $computer
        Online = $false
        Date = (Get-Date -Format g)
    }
}

#Validate the user specified path
if (!$logPath) { $log = $logFile }
elseif (Test-Path $logPath) {
   
    #Replace double-backslashes (\\) with triple to prevent the next peice from breaking UNC storage locations
    if ($logPath -match "\\") { $logPath = $logPath.Replace("\\","\\\") }
   
    #Place a backslash (\) between the logPath and logFile incase it was left out of the path
    #Remove double-backslashes (\\) if one was input already
    $log = ($logPath + "\" + $logFile).Replace("\","\\")
}
else { Write-Host "The logging directory specified is not valid.  Please specify a valid path and try again." ; exit }

#create an array list of all computers being queried.
$list = @()
if ($computers) { $list = $computers }
elseif ($file) { $list = gc $file }
else { $list += "localhost" }

#A bug within Test-Connection will return $false when testing the local computer as "." as the response comes from "localhost"
#Replacing "." with "localhost" to allow validation to correctly function
$list = $list | % { $_.Replace(".","localhost") }

#Create sessions on all computers (remote or local)
$sessions = $list | ? { Test-Connection $_ -quiet -Count 1 } | % {
   
    #Gather credentials and create connections if -cred was specified
    if ($cred) { New-PSSession -ComputerName $_ -Credential (Get-Credential) }
   
    #Otherwise, just create connections
    else { New-PSSession -ComputerName $_ }
}

#Build the command script block to pass into Invoke-Command later
$command = {
   
    #Convert $bytes to MB if it is less then 1GB and convert to GB if it greater than or equal to 1GB
    function ConvertFrom-Bytes {
        <#
            .SYNOPSIS
                Converts from Bytes to MB or GB with two decimal places
            .PARAMETER $bytes
                Int32 value to convert
            .INPUT
                Accepts one Int32
        #>
        param ([Int32]$bytes)
        switch ($bytes) {
            { $bytes -lt 1gb } { ([Math]::Round($bytes/1mb,2)).ToString() + "MB" }
            Default { ([Math]::Round($bytes/1gb,2)).ToString() + "GB" }
        }
    }
   
    #Return $true if $bytes is less than 128MB
    function Check-UpgradeNeed {
        <#
            .SYNOPSIS
                Returns $true if the byte size is less than 128MB and $false if it is greater than or equal to 128MB
            .PARAMETER $bytes
                Int32 value to check
            .INPUT
                Accepts one Int32
        #>
        param ([Int32]$bytes)
        switch ($bytes) {
            { $bytes -lt 128mb } { $true }
            Default { $false }
        }
    }

    #Query the Win32_VideoController WMI class for all video cards
    #This will help determin upgrade requirements on all video cards, not just the primary
    gwmi Win32_VideoController |
   
    #Only return the desired properties to keep the execution as light-weight as possible
    Select VideoProcessor,SystemName,DeviceID,AdapterRAM | % {
       
        #Although we have many of our existing properties the Property Names are a bit ambiguous and we need to add more properties
        #Instead of using Add-Property for the extras we can accomplish the Property Name rename and add additional properties through a single object
        New-Object PSObject -Property @{
            Computer = $_.SystemName
            VideoCard = $_.VideoProcessor
            Device = $_.DeviceID
           
            #Calculating memory size as MB or GB
            VideoMemory = ConvertFrom-Bytes $_.AdapterRAM
           
            #Determine upgrade needs
            NeedsUpgraded = Check-UpgradeNeed $_.AdapterRAM
            Online = $true
            Date = (Get-Date -Format g)
        }
    }
}

#Create the $data object and collect all information into this object
$data = @( & {
   
    #Report if no sessions were able to be generated
    if (!$sessions) {
        $list | % { Report-NotOnline } | Select Computer,VideoCard,Device,VideoMemory,NeedsUpgraded,Online,Date
    }
    else {
       
        #Start the execution of all tasks simultaneously
        #Note again that $sessions contains the credentials, therefore they are not explicitely required for Invoke-Command
        Invoke-Command -Session $sessions -ScriptBlock $command -ErrorAction SilentlyContinue | Select Computer,VideoCard,Device,VideoMemory,NeedsUpgraded,Online,Date
   
        #This will output any non-active computer by compairing the full list of computers with those that a upon which a sessions was able to opened
        #By keeping this and Invoke-Command with the same $data object, they are able to be gathered verry efficiently
        $list | ? {
           
            #Build an array list of all computer names withn a session and compair its contents with the item being passed through the pipe
            @($sessions | % { $_.ComputerName } ) -notcontains $_
        } | % { Report-NotOnline } | Select Computer,VideoCard,Device,VideoMemory,NeedsUpgraded,Online,Date
       
        #Remove all open PSSessions
        $sessions | Remove-PSSession
    }
#Note the trailing Close Parentheses as the $data object is being completed
} )

#Write data gathered to the console if -quiet was not specified
if (!$quiet) { $data | FT }

#Add existing log information into the new $data object if -append was selected
if ((Test-Path $log) -and $append) {
    Import-Csv $log | % { $data += $_ }
}

#Write data to $log in the running directory
$data | Export-Csv $log -NoTypeInformation -Force

Friday, April 30, 2010

Set-EnvVar

Everyone, or anyone, who is listening!  I have made my first submission to the 2010 Scripting Games hosted so generously by the guys at PoShCode.org

I'm trying to code hard and fast, so it may be a while before getting too many comments on the posts from this event.

This script will create System or User Environment Variables on a remote or local machine.  I have also integrated PoSh's method of generating help documentation.  It will be parsed by Get-Help and generate help content similar to any PoSh cmdlet.

<#
.SYNOPSIS
Sets remote and local environment variables

.DESCRIPTION
Set-EnvVar.ps1 sets variables for either the User or System enviroments.  This can be run against a remote computer or on a local session.

.PARAMETER name
Variable name

.PARAMETER value
Variable value

.PARAMETER user
Create the variable in the User space

.PARAMETER system
Create the variable in the System space

.PARAMETER computers
Remote computer(s) to create the variable in.  Seperate with a comma (,) for multiple computers.

.PARAMETER file
File with a list of computers to create the variable in

.PARAMETER cred
Run under specified credentials.  The user will be prompted to enter a username and password for script execution

.PARAMETER quiet
Run silently

.PARAMETER help
Display help information

.INPUTS
None. You cannot pipe objects to Set-EnvVar.ps1.

.OUTPUTS
Set-EnvVar.ps1 outputs Set-EnvVar_log.csv in the same directory as the script

.EXAMPLE
C:\PS> .\Set-EnvVar.ps1 -name CodeRed -value 1980s -system
A system variable will be created with the name CodeRed and value of 1980s.  Output will be shown on the console.  Since neither -file or -computers is used, the script will run locally.

.EXAMPLE
C:\PS> .\Set-EnvVar.ps1 -name CodeRed -value 1980s -user -file list.txt -quiet
A variable with the name CodeRed will be created in the User space with a value of 1980s.  The file list.txt will be parsed for contents and all devices in the list will be updated.  This will be done silently and a log file created as normal.

#>
param (
    [string]$name,        #Variable name
    [string]$value,        #Variable value
    [switch]$user,        #Create the variable in the User space
    [switch]$system,    #Create the variable in the System space
    [array]$computers,    #Remote computer(s) to create the variable in.  Seperate with a comma (,) for multiple computers.
    [string]$file,        #File with a list of computers to create the variable in
    [switch]$cred,        #Run under specified credentials
    [switch]$quiet,        #Silent
    [switch]$help        #Display help information
)

#Check for existence and value of the variable
function Check-EnvVar ($name,$value,$type) {

    #Creates the command to execute on the remote session
    $command = {
        param (
            $name,
            $value,
            $type
        )
        #Create a new object with required information to return back from the function
        New-Object PSObject -Property @{
            Computer = $env:COMPUTERNAME
            Name = $name
            Value = ([System.Environment]::GetEnvironmentVariable($name,$type))
        }
    }
   
    #Execute the command
    if ($credentials) { Invoke-Command -Session $sessions -ScriptBlock $command -ArgumentList $name,$value,$type -ErrorAction SilentlyContinue -Credential $credentials }
    Invoke-Command -Session $sessions -ScriptBlock $command -ArgumentList $name,$value,$type -ErrorAction SilentlyContinue
}

#Create the variable if it does not exist, or set the value if it does
function Create-EnvVar ($name,$value,$type) {

    #Creates the command to execute on the remote session
    $command = {
        param (
            $name,
            $value,
            $type
        )
        [System.Environment]::SetEnvironmentVariable($name,$value,$type)

        #Create a new object with required information to return back from the function
        New-Object PSObject -Property @{
            Computer = $env:COMPUTERNAME
            Date = (Get-Date).ToString()
            Result = if ($Error) {$error[0].Exception.Message ; $error.Clear()} else {"Success"}
        }
    }
   
    #Execute the command
    if ($credentials) { Invoke-Command -Session $sessions -ScriptBlock $command -ArgumentList $name,$value,$type -ErrorAction SilentlyContinue -Credential $credentials }
    else { Invoke-Command -Session $sessions -ScriptBlock $command -ArgumentList $name,$value,$type -ErrorAction SilentlyContinue -Credential $credentials }
}

#Validate all required user input before beginning execution
if ($user -and $system) { "You may only select -System or -User, not both" ; Exit }
elseif ($user) { $type = "User" }
elseif ($system) { $type = "Machine" }
else { "You must select -System or -User" ; Exit }

if (!$name) { $name = Read-Host "Please input the Name of the environment variable you wish to create." }
if (!$value) { $value = Read-Host "Please input the Value of the environment variable you wish to create." }

if ($file) { $sessions = nsn (gc $file) }
elseif ($computers) { $sessions = $computers | % { nsn -ComputerName $_ } }
else { $sessions = nsn -ComputerName . }

if ($cred) { $credentials = Get-Credential }

#Check for existence and value of the variable using the Check-EnvVar function
$check = Check-EnvVar $name $value $type | % {
   
    #Ask for user input if the variable already exists, or bypass if the -Quiet switch is enabled or the variable does not exist.
    if (!$quiet -and $_.Value) {
        Read-Host ("`n" + $_.Computer + "`n" + $_.Name + "=" + $_.Value + "`nDo you want to overwrite this entry.`n(Yes/No)") | % {
            if ($_ -match "y") { $change = $true }
            else { $change = $false }
        }
    }
    else { $change = $true }
   
    #Create a new object with required information to enter into $check
    New-Object PSObject -Property @{
        Computer = $_.Computer
        Change = $change
        Value = $_.Value
    }
}

#Remove any sessions on which the variable will not be changed if .Change is not $true.
$check | ? {$_.Change -ne $true} | % {
    if ($env:COMPUTERNAME -match $_.Computer) { Remove-PSSession -ComputerName "localhost" }
    else { Remove-PSSession -ComputerName $_.Computer }
    $sessions = Get-PSSession
}

#If there are any remainging sessions created by the script, execute the change.
if ($sessions) {
    $create = Create-EnvVar $name $value $type | Select Computer,Result,Date
    $sessions | Remove-PSSession
}

#If not, create a log noting no remaining sessions.
else {
    New-Object PSObject -Property @{
        Computer = "none"
        Date = (Get-Date).ToString()
        Result = "all computers have been removed from the change list."
    } | Select Computer,Result,Date | Export-Csv "Set-EnvVar_Log.csv" -NoTypeInformation
}

#Merge the checked list with the change list for a unified log
if ($check) {
    $check | % {
        $checked = $_
        $create | ? { $checked.Computer -eq $_.Computer } | % {
           
            #Create a new object with required information for the final log
            New-Object PSObject -Property @{
                Computer = $checked.Computer
                Variable = $name
                PreviousValue = $checked.Value
                NewValue = $value
                Result = $_.Result
                Date = $_.Date
            }
        }
   
    #Creates the final log
    } | Select Computer,Variable,PreviousValue,NewValue,Result,Date | Export-Csv "Set-EnvVar_Log.csv" -NoTypeInformation
}

#Log errors
if ($error) {
    $error | % {
        $_ | Out-File "Set-EnvVar_errors.log" -Append
    }
    "Errors were reported.  Please check Set-EnvVar_errors.log"
    $error.Clear()
}

Wednesday, March 10, 2010

Reading Events

I think most of us are aware of the Get-EventLog cmdlet in v2.0. Personally, I think it is, but the event logs themselves are a bit messy. For example, EventID 4663 "An attempt was made to access an object". One big problem is that their is missing information in the object. Fortunately there is the ReplacementStrings contains all the data from the message that does not show as a value of the event object property.

Confused yet? Here's an example:

EventID            : 4663
MachineName        : xxx.mydomain.local
Data               : {}
Index              : 93068
Category           : (12800)
CategoryNumber     : 12800
EntryType          : SuccessAudit
Message            : An attempt was made to access an object.
                     
                     Subject:
                         Security ID:        S-1-5-xx-xxxxxxxxxx-xxxxxxxxx-xxxx
                     914379-1106
                         Account Name:        johndoe
                         Account Domain:        MYDOMAIN
                         Logon ID:        0x251942
                     
                     Object:
                         Object Server:    Security
                         Object Type:    File
                         Object Name:    C:\Temp\SecuredFolder
                         Handle ID:    0xa18
                     
                     Process Information:
                         Process ID:    0x4
                         Process Name:    
                     
                     Access Request Information:
                         Accesses:    %%1537
                                 
                         Access Mask:    0x10000
Source             : Microsoft-Windows-Security-Auditing
ReplacementStrings : {S-1-5-21-XXXXXXXXXX-XXXXXXXXX-XXXXXXXXXX-XXXX, johndoe, M
                     YDOMAIN, 0x251942...}
InstanceId         : 4663
TimeGenerated      : 2/30/2010 12:00:00 PM
TimeWritten        : 2/30/2010 12:00:00 PM
UserName           : 
Site               : 
Container          : 
 
As you can see the UserName property is blank, yet it does show in the the message. Now, look at the data in ReplacementStrings. In this object you'll see the same entry from the line Account Name in the message as the 2nd string, johndoe. Since ReplacementStrings is an object, we'll pull the 2nd entry as [1]. (Remember that the first item in an object is 0). The same is true for the domain. It is set in the 3rd entry [2]. The object accessed is the 7th [6]....so on down the line.

Now, with that information out of the way, lets pull some information.

CODE
get-eventlog security -InstanceId 4663 |
     Select TimeGenerated,ReplacementStrings |
     % {
         New-Object PSObject -Property @{
            TimeGenerated = $_.TimeGenerated
            UserName = $_.ReplacementStrings[2] + "\" + $_.ReplacementStrings[1]
            Object = $_.ReplacementStrings[6]
            Access = $_.ReplacementStrings[8]
        }
     }
 code: copy : expand : collapse


Now when this is run you'll see a formatted output to show as below:


Object                                       TimeGenerated                      UserName
------                                        -------------                          --------
C:\Temp\SecuredFolder            2/30/2010 12:00:00 PM         MYDOMAIN\johndoe
 
Hopefully this helps someone the get information from event logs more efficiently.