Friday, April 10, 2009

Powershell and remote WMI autenthication

In version 1.0 of Windows Power­Shell, gmwi (which is an Alias for Get-WmiObject) is about the only cmdlet that directly supports remote management. This is due mainly to the fact that remote control is built into the underlying Windows Management Instrumentation architecture. And, because Windows PowerShell is simply utilizing that already existing architecture, it’s subject to that architecture’s security features.

Beeing able to perform remote WMI queries is particularly useful when doing a computer inventory for big companies having different domains and forests.

Here’s an example of standard gwmi query:

gwmi -namespace “root\cimv2” -computer $Comp -list

Get-WmiObject : Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)) At line:1 char:5

In this instance, I tried to connect to a remote computer called $Comp that I don’t have permission to access. I might, for example, be logged on in a different, untrusted active directory domain, or I could be logged on with a less-privileged account.*

Fortunately, gwmi supports a –credential parameter that allows me to specify an alternate set of user credentials for my WMI connection.

gwmi win32_service –credential mydomain\administrator –computer SComp

My username—is provided in the DOMAIN\Username format.

Note that there’s nowhere you can enter a password. Windows PowerShell will prompt me for that. Windows PowerShell deliberately doesn’t provide a way to enter a password on the command line because doing so would enable you to hardcode passwords into script files, which is an absolute security risk.

However, there are other ways you can work with this –credential parameter

The first one is by creating a sort of credential object, called a PSCredential, ahead of time.

The key is the Get-Credential cmdlet:

$cred = get-credential mydomain\administrator

When I run this, I’m still prompted for the matching password. This time, however, the credential object that is created is stored in the $cred variable. If I look at the contents of $cred, I’ll see the name but not the password:

$cred

UserName

--------

mydomain\administrator

I can then reuse that credential object as much as I like:

gwmi win32_service –credential $cred –computer $Comp

This simplifies repeated WMI connections to a remote computer by predefining a reusable credential object. But I still have to type my password once at the beginning of the script, which is not good if we'd want to schedule the script execution...

The best solution is to previously store the password in a secure file on your disk. Once you store you password, you won't have to enter your credentials over and over again, instead you can just read in your password from the file directly from the script, and create a new PSCredential object from that. Then you can use that credential to perform various unattended administration tasks:

$user = "mydomain\administrator"

$pass = cat securepass.txt | convertto-securestring

$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $user,$pass

For more information on how to store your password in a file, have a look at this post

Please note (and this is very important) that only the person that created the password file can decrypt it. Try it out, log on with different credentials and try getting the password. Thus, even if they have access to the password file, unless they have your credentials, they won't be able to get the password.

Wednesday, April 8, 2009

Store a secure password in a file with Powershell

## ===================
## This is a one-liner to ask for a password a store it safely in a
securepass.txt file
read-host -prompt "Enter password to be encrypted in securepass.txt "
-assecurestring | convertfrom-securestring | out-file securepass.txt
## ===================

NSLOOKUP and POWERSHELL

# FORWARD DNS RESOLUTON WITH NSLOOKUP
# This function calls nslookup (which is a standard microsoft tool)
# and fetches from the output string just the IP address.
# You can specify a Name Server to use.
# Please note the $Comp can be an array, in which case you should
# run into it with foreach().
# ==========================================
Function forward_dns
{
$cmd = "nslookup " + $args[0] + " " + $ns
$result = Invoke-Expression ($cmd)
trap
{
$global:controlladns = $true
$global:solved_ip = "No record found"
continue
}
$global:controlladns = $false
$global:solved_ip = $result.SyncRoot[4]
if (-not $global:controlladns)
{
$leng = $global:solved_ip.Length -10
$global:solved_ip =
$global:solved_ip.substring(10,$leng)
}
}
$comp = "hostname" # Hostname to resolve to IP
$ns = "DNS name" # Name Server which will do name resolution
forward_dns $comp
echo "Host: $comp - IP : $global:solved_ip - NS: $ns"
# ==========================================


# REVERSE DNS RESOLUTON WITH NSLOOKUP
# This function calls nslookup (which is a standard microsoft tool)
# and fetches from the output string just the hostname.
# You can specify a Name Server to use.
# Please note the $Comp can be an array, in which case you should
# run into it with foreach().
# ==========================================
Function reverse_dns
{
$cmd2 = "nslookup " + $args[0] + " " + $ns
$result2 = Invoke-Expression ($cmd2)
$global:reverse_solved_ip = $result2.SyncRoot[3]
if ($result2.count -lt 4) # Integrity check
{
$global:reverse_solved_ip = "No record found"
}
else
{
$leng2 = $global:reverse_solved_ip.length - 9
$global:reverse_solved_ip =
$global:reverse_solved_ip.substring(9,$leng2)
}
}
$comp = "X.X.X.X" # Hostname to resolve to IP
$ns = "DNS name" # Name Server which will do name resolution
reverse_dns $comp
echo "IP: $comp - Host : $global:reverse_solved_ip - NS: $ns"
# ==========================================

# ==========================================
# Don't use DIG or get-dns.
# You don't need to install any add-on just for name resolution.
# Remember to open PORT 53 on you firewall for DNS requests.
# ==========================================

# ==========================================
# Note also the use of the ".substring" method.
# Example:
# $a="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
# $a = $a.substring(0,3)
# When you run this command and then echo back the value of $a you should
get the following:
# ABC
# ==========================================

Powershell Operators

-eq Equal (case insensitive)
-ne Not equal (case insensitive)
-ge Greater than or equal (case insensitive)
-gt Greater than (case insensitive)
-lt Less than (case insensitive)
-le Less than or equal (case insensitive)
-like Wildcard comparison (case insensitive)
-notlike Wildcard comparison (case insensitive)
-match Regular expression comparison (case insensitive)
-notmatch Regular expression comparison (case insensitive)
-replace Replace operator (case insensitive)
-contains Containment operator (case insensitive)
-notcontains Containment operator (case insensitive)
## ====================================
-ieq Case insensitive equal
-ine Case insensitive not equal
-ige Case insensitive greater than or equal
-igt Case insensitive greater than
-ile Case insensitive less than or equal
-ilt Case insensitive less than
-ilike Case insensitive equal
-inotlike Case insensitive equal
-imatch Case insensitive regular expression comparison
-inotmatch Case insensitive regular expression comparison
-ireplace Case insensitive replace operator
-icontains Case insensitive containment operator
-inotcontains Case insensitive containment operator
## ====================================
-ceq Equal (case sensitive)
-cne Not equal (case sensitive)
-cge Greater than or equal (case sensitive)
-cgt Greater than (case sensitive)
-clt Less than (case sensitive)
-cle Less than or equal (case sensitive)
-clike Wildcard comparison (case sensitive)
-cnotlike Wildcard comparison (case sensitive)
-cmatch Regular expression comparison (case sensitive)
-cnotmatch Regular expression comparison (case sensitive)
-creplace Replace operator (case sensitive)
-ccontains Containment operator (case sensitive)
-cnotcontains Containment operator (case sensitive)
## ====================================
-is Is of a type
-isnot Is not of a type
-as As a type, no error if conversion fails

Tuesday, April 7, 2009

Powershell function to check last patches

This function uses WMI to connect to a remote host and retrieve the three last patches and their installation date. It uses the WQL (WMI query language) to fetch just the last three installed patches and not all of them.

Prerequisites:

You need an array named $computerlist containing a list of computers, i.e.
$computers = "host01","host02","host03"

And you need your credentials stored in a secure $creds variable.

You could have used the registry, but in Powershell 1.0 you must une a .NET class which doesn't support remote autenthication in a different security context (i.e. a different AD domain).

Please note that the Win32_QuickFixEngineering WMI class has a know bug and not all the pathces are listed... this is a very old problem.

This is the code:

#*=============================================================================
#* Function: QFE
#* Arguments: $Comp
#* Output: $patchid01/02/03 and $patchdate01/02/03
#* Purpose: Retrieve the 3 last patches
#*
#*=============================================================================
function QFE
{
$global:checkkb = gwmi Win32_QuickFixEngineering -ComputerName $args[0] -credential $creds | ? { $_.InstalledOn } | sort installedon | select -Last 3 #| ft hotfixid, installedon
#$global:checkkb
$global:patchid01 = $global:checkkb.syncroot[0].hotfixid
$global:patchdate01 = $global:checkkb.syncroot[0].installedon
$global:patchid02 = $global:checkkb.syncroot[1].hotfixid
$global:patchdate02 = $global:checkkb.syncroot[1].installedon
$global:patchid03 = $global:checkkb.syncroot[2].hotfixid
$global:patchdate03 = $global:checkkb.syncroot[2].installedon
$global:patchid01 + " " + $global:patchdate01
$global:patchid02 + " " + $global:patchdate02
$global:patchid03 + " " + $global:patchdate03
$global:checkkb = $null
}
foreach ($comp in $computerlist) {QFE}