Wednesday, September 5, 2012

PowerShell: WMI to Report Installed Applications on Remote Systems

Recently, I've been working with more PowerShell Remoting. Earlier this week I wrote a script that queries a list of systems for installed applications using Remoting. Tomorrow, I'm demo'ing that script (which I will post after this one) and needed to show how you would do it using just WMI in PowerShell. Since the listing of installed applications is stored in the Registery it took a good amount of time trying to figure out how to access a remote registry via PowerShell just using WMI. Below is the comparison script. Enjoy.


#########################################################
# Script Name: PS_Remote_WMI_Installed_Applications.ps1
# Version: 1.0
# Description: Using WMI Remotely Queries
#               Systems for Installed Software
#########################################################

#Array for Reporting Installed Software
$installedApps = @();

#Array for Registry Paths to Installed Apps
$appRegPaths = @("Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
                 "Software\Microsoft\Windows\CurrentVersion\Uninstall");

#Array of System Names to Run Against
$computers = @("SERVER01","SERVER02","SERVER03");

foreach($computer in $computers)
{
    #Ping System First
    if(Test-Connection -ComputerName $computer -Quiet)
    {
        #Connect to WMI Registry Class
        $uReg = [wmiclass]"\\$computer\root\default:StdRegProv";
                          
        foreach($regPath in $appRegPaths)
        {
            #Pull the Application Registry Keys 
            $iAppKeys = $uReg.EnumKey(2147483650,$regPath);
    
            #Null Check on Application Registry Keys
            if($iAppKeys)
            {
                #Loop Through Each Application Key
                foreach($appKey in $iAppKeys.sNames)
                {
                    #Construct Key Path
                    $keyPath = $regPath + "\" + $appKey.ToString();
                    
                    #Pull the Key DisplayName String Value
                    $keyDisplayName = $uReg.GetStringValue(2147483650,$keyPath,"DisplayName");
                    if(![string]::IsNullOrEmpty($keyDisplayName.sValue))
                    {
                        #Local Vars Used for Reporting
                        [string]$displayName = $keyDisplayName.sValue.ToString();
                        [string]$displayVersion = "";
                    
                        #Pull the Key DisplayVersion String Value
                        $keyDisplayVersion = $uReg.GetStringValue(2147483650,$keyPath,"DisplayVersion");
                        if(![string]::IsNullOrEmpty($keyDisplayVersion.sValue))
                        {
                            $displayVersion = $keyDisplayVersion.sValue.ToString();
                        }
                
                        #Create Custom PSObject and Add to Reporting Array
                        $app = New-Object PSObject;
                        $app | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $computer;
                        $app | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value $displayName;
                        $app | Add-Member -MemberType NoteProperty -Name "DisplayVersion" -Value $displayVersion;
                          $installedApps += $app;
                        
                    }#End of Null\Empty Check on DisplayName String Value
                    
                }#End of Foreach $iAppKeys
            
            }#End of Null Check on $iAppKeys
        
        }#End of Foreach Reg Path
    
    }#End of Test-Connection

}#End of Foreach Computer

$installedApps | Sort-Object ComputerName,DisplayName | Format-Table -AutoSize;

Friday, August 10, 2012

PowerShell: WMI Basics

During my lunch hour today, I showed a few colleagues how to use of PowerShell with WMI. We mainly covered the Get-WMIObject command and a lot of fun things you can do with it. Below are the examples we went over. Enjoy.
############################################################
# WMI PowerShell Commands
############################################################

#Use -ComputerName Option with Hostname, FQDN, or IP Address in Command for Remote Systems
#For Example to Get the BIOS Settings On a System Called DeanTestServer
Get-WmiObject -Query "SELECT * FROM Win32_BIOS" -ComputerName "DeanTestServer"
#Or Use the IP Address
Get-WmiObject -Query "SELECT * FROM Win32_BIOS" -ComputerName "192.168.2.25"

#Get-WMIObject Can Use A WOL Query, Filter, or PowerShell Where Statement to Limit Results
#For Example the Following Three Commands Have the Same Result
Get-WmiObject -Class Win32_Share | Where-Object { $_.Name -eq "C$" }
Get-WmiObject -Class Win32_Share -Filter "Name='C$'"
Get-WmiObject -Query "SELECT * FROM Win32_Share WHERE Name='C$'"

#Get All Win_32 Classes in the CIMV2 Namespace
Get-WmiObject -Namespace "root\cimv2" -List | Where-Object { $_.Name -like "Win32_*" } | Select-Object Name | Sort-Object Name | Out-File WMI_CIMV2_Classes.txt

#Get All Properties and Methods for the WMI Class
Get-WmiObject Win32_Volume | Get-Member

#Get Basic System Information
Get-WmiObject -Query "SELECT * FROM Win32_ComputerSystem"

#Get Local Accounts and Groups on a System
Get-WmiObject -Query "SELECT * FROM Win32_Account" | Select-Object Name,SID | Sort-Object Name

#Get Disk Information (Model and Size)
Get-WmiObject -Query "SELECT * FROM Win32_DiskDrive"

#Get Processor Information
Get-WmiObject -Query "SELECT * FROM Win32_Processor" | Select-Object Name,Description,NumberOfCores | Sort-Object Name 

#Get Operating System Info
Get-WmiObject -Query "SELECT * FROM Win32_OperatingSystem"

#Get MAC Addresses of All Network Adapters
Get-WmiObject -Query "SELECT * FROM Win32_NetworkAdapter WHERE MACAddress IS NOT NULL" | Select-Object Name,MACAddress | Sort-Object Name;

#Get All Assigned IPs 
Get-WmiObject -Query "SELECT * FROM Win32_NetworkAdapterConfiguration" | Where-Object { $_.IPAddress -ne $null} | Select-Object Description,IPAddress;

#List Number of Memory Slots on a System
Write-Output ("Number of Memory Slots: " + (Get-WmiObject -Query "SELECT * FROM win32_PhysicalMemoryArray").MemoryDevices);

#Retrieve Memory Slot Allocations
Get-WMIObject -Query "SELECT * FROM Win32_PhysicalMemory" | ForEach-Object { Write-Output ($_.DeviceLocator.ToString() + " " + ($_.Capacity/1GB) + "GB") };

#Retrieve Disk Volume Sizes (Including Mount Points, Excluding Pass Through Drives)
$sysVolumes = Get-WmiObject –Query "Select * FROM Win32_Volume WHERE DriveType=3 AND NOT Name LIKE '%?%'" | Sort-Object Name;
foreach($sv in $sysVolumes)
{
    #Var for Volume Size
    $vSize = "{0:N2}" -f ($sv.Capacity/1GB);
    #Var for Free Space 
    $vFS = "{0:N2}" -f ($sv.FreeSpace/1GB);
    #Var for Percentage Free Space
    $vPF = "{0:N2}" -f (($sv.FreeSpace/$sv.Capacity)*100);
    #Var for Drive Letter
    $vLetter = $sv.Name.ToString().TrimEnd("\");
    $VolumeStatus = "$vLetter | Size(GB): $vSize | Free Space(GB): $vFS | Percentage Free: $vPF"; 
    Write-Output $VolumeStatus;
}



Friday, July 20, 2012

PowerShell: Server Drive Space Report

Below is the PowerShell script code that generates an email report of drive space sizes on servers listed in an  array.


#Error Handling
$erroractionpreference = "SilentlyContinue";

#Function for Email Notices
function uEmailNotice([string]$msgBody,[string]$msgSubject)
{
    #Variable for Email FROM Address
    $mFrom = "fromAddress@mycollege.edu";
    #Variable for EMail TO Address
    $mTo = "toAddress@mycollege.edu";
    #Variable for SMTP Server
    $smtp = "smtpServer.mycollege.edu";

    #Settings for Email Message
    $messageParameters = @{                        
                            Subject = $msgSubject
                            Body = $msgBody                       
                            From = $mFrom                        
                            To = $mTo                        
                            SmtpServer = $smtp                       
                           };                        
    #Send Report Email Message 
    Send-MailMessage @messageParameters –BodyAsHtml;
}

#Var for Disk Percentage to Check
$percentCheck = 12;

#Array for Server Names
$Servers = @(
              "server1.mycollege.edu",
              "server2.mycollege.edu",
              "server3.mycollege.edu",
              "server4.mycollege.edu"
             );



#Array for Systems with Low Free Disk Percentages
$arLFDP = @();

#Var for HTML Message Body
$msgBody = "<html>
            <body>
            <h3>Servers Disk Space Report</h3>";
            
#Var for HTML All Server Table Info
$sTableInfo    = "<table border=""0"" cellpadding=""5"" cellspacing=""2"" style=""font-size:8pt;font-family:Arial,sans-serif"">
               <tr bgcolor=""#000099"">
                <td><strong><font color=""#ffffff"">Server</font></strong></td>
                <td><strong><font color=""#ffffff"">Drive</font></strong></td>
                <td><strong><font color=""#ffffff"">Size (GBs)</font></strong></td>
                <td><strong><font color=""#ffffff"">Free Space (GBs)</font></strong></td>
                <td><strong><font color=""#ffffff"">% Free</font></strong></td>
               </tr>";
               
#Loop Through All Servers
foreach($server in $Servers)
{
    #Pull Server Name from FQDN
    $sName = ($server.ToString().Split("."))[0].ToString().ToUpper();
    
    #Compose Table Row for Server Name
    $sTableInfo += "<tr bgcolor=""#dddddd"" cellspacing=""0"">
                    <td>$sName</td>
                     <td colspan=""4""></td> 
                     </tr>";
    
    #Ping Computer Before Attempting Remote WMI 
      if(test-connection -computername $server -quiet) 
      {
        #Make WMI Call to Remote Server
        $sysDrives = Get-WmiObject –Query "Select * FROM Win32_LogicalDisk WHERE DriveType=3" -ComputerName $server;
        
        #Null Check on $sysDrives  
        if($sysDrives)
        {
            #Loop Through Each Logical Disk on Server
            foreach($drive in $sysDrives)
            {
                #Var for Percentage Free Space
                $dPF = "{0:N2}" -f (($drive.FreeSpace / $drive.Size) * 100);
                #Var for Free Space
                $dFS = "{0:N2}" -f ($drive.FreeSpace / 1GB);
                #Var for Disk Size
                $dSize = "{0:N2}" -f ($drive.Size / 1GB);
                #Var for Drive Letter
                $dLetter = $drive.DeviceID.ToString();
                #Double for Percentage Free Comparison
                $freePercent = [double]$dPF.ToString();
                
                #Check to See If Drive Percentage Free Is Greater Than or Equal to Set Alert Amount
                if($freePercent -ge $percentCheck)
                {
                    #Add Disk Info 
                    $sTableInfo += "<tr>
                                    <td></td>
                                    <td>$dLetter</td>
                                     <td>$dSize</td>
                                     <td>$dFS</td>
                                     <td>$dPF</td>
                                     </tr>";
                    
                }
                else
                {    
                    #Add Disk Info with Alert Formatting
                    $sTableInfo += "<tr>
                                    <td></td>
                                    <td><font color=""#ff0000"">$dLetter</font></td>
                                     <td><font color=""#ff0000"">$dSize</font></td>
                                     <td><font color=""#ff0000"">$dFS</font></td>
                                     <td><font color=""#ff0000"">$dPF</font></td>
                                     </tr>";
                    
                    #Create PS Object for Low Disk Space Alert
                       $uEntry = new-Object PSObject;
                       $uEntry | add-Member -memberType noteProperty -name "Server" -Value $sName.ToString().ToUpper();
                       $uEntry | add-Member -memberType noteProperty -name "Drive" -Value $dLetter.ToString();
                       $uEntry | add-Member -memberType noteProperty -name "Percentage" -Value $dPF.ToString();
                       #Add Entry to Summary Array
                       $arLFDP += $uEntry;
                    
                }#End of Percentage Free Check
                                          
            }#End of Foreach Drive
            
        }
        else
        {
            #RPC Not Avaialable
            $sTableInfo += "<tr>
                            <td></td>
                             <td colspan=""4""><font color=""#ff0000"">RPC Not Available</font></td> 
                           </tr>";
            
        }#End of $sysDrives Null Check
        
    }
    else
    {
        #Server Not Pingable
        $sTableInfo += "<tr>
                        <td></td>
                         <td colspan=""4""><font color=""#ff0000"">Ping Failed</font></td> 
                        </tr>";
        
    }#End of Ping Test
    
    #Add Blank Line After Server Info Placed (Readability)
    $sTableInfo += "<tr>
                    <td colspan=""5""></td> 
                    </tr>";
}

#Write Alerts to HTML Message Body If Any
if($arLFDP.Count -gt 0)
{
    $msgBody += "<strong>Servers with Drives Less than $percentCheck% Free</strong><br />
                <table border=""0"" cellpadding=""5"" cellspacing=""2"" style=""font-size:8pt;font-family:Arial,sans-serif"">
                   <tr bgcolor=""#ff0000"">
                    <td><strong><font color=""#ffffff"">Server</font></strong></td>
                    <td><strong><font color=""#ffffff"">Drive</font></strong></td>
                    <td><strong><font color=""#ffffff"">% Free</font></strong></td>
                 </tr>";

    foreach($alert in $arLFDP)
    {
        $msgBody += "<tr><td>" + $alert.Server.ToString() + "</td><td>" + $alert.Drive.ToString() + "</td><td>" + $alert.Percentage.ToString() + "</td></tr>";
    }

    $msgBody += "</table>
                 <br />";
}

#Title All Servers Table
$msgBody +=  "<strong>All Servers</strong><br />";

#Add Servers Table Info to Message Body
$msgBody += $sTableInfo;

#Close HTML Table and Message
$msgBody += "</table>
            </body>
            </html>";
            
#Get Current Short Date
$rptDate = Get-Date -Format d;

#Format Message Subject
$msgSubject = "Servers Disk Space Report for " + $rptDate;

#Email Report
uEmailNotice $msgBody $msgSubject;



PowerShell: Useful Commands for Beginners

During lunch today I presented the first installment of a "Beginning PowerShell" series for colleagues at my work. We covered setting up the console and command syntax. Below are some of the commands we used during the session. 



#Get Version of PowerShell Running on System
$PSVersionTable

#Start a Transcript File
Start-Transcript C:\Users\userID\desktop\MyTranscript.txt
#Or for the File to be Placed in the Current Directory
Start-Transcript "MyTranscript.txt"
#Or for the Default Location (..\Do C:\Users\userID\Documents\PowerShell_transcript.NNNNN.txt
Start-Transcript

#To Stop the Transcript from Recording Commands and Output
Stop-Transcript

#Set the Script Execution Policy for Current User 
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

#Get All Currently Loaded PowerShell Snapins
Get-PSSnapin

#Get All Commands in a Specific PowerShell Snapin
Get-Command -pssnapin NameOfPSSnapin #(e.g. Microsoft.PowerShell.Security)

#Get All PowerShell Modules Available on System
Get-Module -ListAvailable

#Import Module in Current PowerShell Session
Import-Module NameOfModule #(e.g. ActiveDirectory)

#Get All Commands in a Module (Should Only Be Used After Importing)
Get-Command -Module NameOfModule

#Get All Currently Loaded Cmdlets
Get-Command -CommandType Cmdlet

#Online Help for a Cmdlet
Get-Help NameOfCmdlet -Online

#Find .NET Object Used in Cmdlet
NameOfCmdlet | Get-Member


Saturday, July 7, 2012

PowerShell: SQL Stored Procedure that Requires a Parameter

Below is a PowerShell code example of how to use a stored procedure (that requires a parameter) on a remote SQL Server. Additionally, I'm using Yes\No prompt (found the code example here)

#Var for SQL Server FQDN 
[string]$SQLServerFQDN = "MySQLServer.mycollege.edu";

#Var for SQL Instance Name
[string]$SQLInstance = "MyInstanceName";

#Var for SQL Database
[string]$SQLDatabase = "MyDatabaseName";

#Var for Insert Stored Procedure Name
[string]$spInsertServer = "Insert_New_Test_Server";

#Connection String Settings (Using Integrated Security So No UserID and Password Needed)
[string]$sqlConString = "Server=$SQLServerFQDN\$SQLInstance;Database=$SQLDatabase;Integrated Security=SSPI;";

#Load .NET System.Data DLL
[Void][system.reflection.assembly]::LoadWithPartialName("System.Data");

#Read In the New Server Name
[string]$newServer = Read-Host "Enter New Server's FQDN";

#Check to See If the Name is Null or Empty
if(![string]::IsNullOrEmpty($newServer))
{
    #Compose Message for Choice Prompt
    [string]$message = "Is " + $newServer + " the Correct FQDN?";

    #Prompt Choices and Options
    $yes = New-Object System.Management.Automation.Host.ChoiceDescription("&Yes");
    $no = New-Object System.Management.Automation.Host.ChoiceDescription("&No");
    $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no);
    #Create Choice Prompt and Assign Reponse Value (Default Set for No)
    $choice = $host.ui.PromptForChoice("New Server", $message, $options, 1);

    #Check to See If User Chose Yes (0=Yes,1=No)
    if($choice -eq 0)
    {
        #Create a SQL Connection Object 
        $sqlCon = New-Object System.Data.SqlClient.SqlConnection($sqlConString);

        #Create and Configure a SQL Command
        $sqlCommSI = New-Object System.Data.SqlClient.SqlCommand($spInsertServer,$sqlCon);

        #Set the Command Type as a Stored Procedure
        $sqlCommSI.CommandType = [System.Data.CommandType]::StoredProcedure;

        #Add Required Parameters (In This Case Just the Server Name)
        [Void]$sqlCommSI.Parameters.Add("@serverName", [System.Data.SqlDbType]::NVarChar);
        $sqlCommSI.Parameters["@serverName"].Value = $newServer.ToUpper();

        #Open the SQL Connection
        $sqlCon.Open();

        #Execute Insert Stored Procedure
        $cmdStatus = $sqlCommSI.ExecuteNonQuery();

        #Close the SQL Connection
        $sqlCon.Close();

        #Check to See If Command Successfully Completed
        if($cmdStatus -eq 1)
        {
            Write-Output "Command Completed Successfully";
        }
        else
        {
            Write-Output "No Go At This Station";
        }
        
    }
    else
    {
        Write-Output "Please Start Script Over";
    }#End of Choice Check
}
else
{
    Write-Output "Nothing Entered. Please Start Script Over";
}#End of New Server Name Null or Empty Check

PowerShell: Query Remote SQL Server Data Using Stored Procedure

Found a while back a cool blog entry on "Database Access within PowerShell". Using that as a reference, I started configuring a few scripts to utilize stored procedures when accessing SQL Server data. Below is a quick example. Enjoy.
#Var for SQL Server FQDN 
$SQLServerFQDN = "MySQLServer.mycollege.edu";

#Var for SQL Instance Name
$SQLInstance = "MyInstance";

#Var for SQL Database
$SQLDatabase = "MyDatabase";

#Create Empty Array for Storing Server Names
$servers = @();

#Load .NET System.Data DLL
[Void][system.reflection.assembly]::LoadWithPartialName("System.Data");

#Connection String Settings (Using Integrated Security So No UserID and Password Needed)
$sqlConString = "Server=$SQLServerFQDN\$SQLInstance;Database=$SQLDatabase;Integrated Security=SSPI;";

#Stored Procedure Name for Servers Select Statement
$spSelectServers = "Get_All_Servers";

#Create a SQL Connection Object 
$sqlCon = New-Object System.Data.SqlClient.SqlConnection($sqlConString);

#Create and Configure a SQL Command
$sqlCommSR = New-Object System.Data.SqlClient.SqlCommand($spSelectServers,$sqlCon);

#Set the Command Type as a Stored Procedure
$sqlCommSR.CommandType = [System.Data.CommandType]::StoredProcedure;

#Open the SQL Connection
$sqlCon.Open();

#Execute the Command
$sqlRdrSR = $sqlCommSR.ExecuteReader();

#Read Through the Returned Data
#This Specific Stored Procedure Returns a "Server_Name" Column
while ($sqlRdrSR.Read()) 
{
    $servers += $sqlRdrSR["Server_Name"].ToString().Trim();
}

##Close the SQL Reader and Connection
$sqlRdrSR.close();
$sqlCon.close();

#Loop Through Array and Output Server Name
foreach($server in $servers)
{
    Write-Output $server;
}


Wednesday, April 25, 2012

C# and PowerShell EHLO Response

Earlier today, one of my colleagues put down a challenge to create in .NET and PowerShell a way detect if a remote email server was running Exchange via a EHLO response. Below are the code examples that completed the task.


C# (Console Application)
//Var for TCP Port
Int32 port = 25;
//Byte Array for Response Data
Byte[] rData = new Byte[256];
//Var for Converted Response Data
string responseData = string.Empty;
//Create a TcpClient
TcpClient client = new TcpClient("smtp.dept.mycollege.edu", port);
//Byte Array for Message to Be Sent
Byte[] data = System.Text.Encoding.ASCII.GetBytes("EHLO");
//Initate a Stream
NetworkStream stream = client.GetStream();
//Send Message to Server
stream.Write(data, 0, data.Length);
//Initiate Int for Return Bytes and Read Response of Stream
Int32 bytes = stream.Read(rData, 0, rData.Length);
//Convert Response
responseData = System.Text.Encoding.ASCII.GetString(rData, 0, bytes);
//Write Out Response 
Console.WriteLine(responseData);
//Close Stream and Client
stream.Close();
client.Close();


PowerShell
#Var for TCP Port
[int]$port = 25;
#Byte Array for Response Data
$rData = New-Object Byte[](256);
#Var for Converted Response Data
$responseData = "";
#Create a TcpClient
$tClient = New-Object System.Net.Sockets.TcpClient("smtp.dept.mycollege.edu",$port);
#ASCII Encoder
$encode = [System.Text.Encoding]::ASCII
#Byte Array for Message to Be Sent
[Byte[]]$sData = $encode.GetBytes("EHLO");
#Initiate a Stream
$stream = $tClient.GetStream();
#Send Message to Server
$stream.Write($sData,0,$sData.Length);
#Initiate Int for Return Bytes and Read Response of Stream
[int]$bytes = $stream.Read($rData,0,$rData.Length);
#Convert Response
$responseData = $encode.GetString($rData,0,$bytes);
#Notify User 
Write-Host $responseData;
#Close Stream and Client
$stream.Close();
$tClient.Close();