Google

2014-01-21

dsget parsing in PowerShell vs cmd

Recently, we had a discussion in a Microsoft Certified Professionals group about how to detect who joined a computer to domain. One of the folks suggested using 'qused' option of 'dsget'. It justs returns a number, so you really need to get the count for all users and then see whose number got higher etc., but that's not the reason I am jotting this down.

It's about an unhelpful message from dsquery and how PowerShell parsing of commands from cmd.exe could be problematic.

So the command we are interested in is:
dsget user <DN> -part <AD Partition> -qused -display

E.g.
dsget user "CN=adil,OU=xyz Users,DC=xyz,DC=org" -part dc=xyz,dc=org -qused -display


Instead of typing  DistinguishedName of the user, it is also possible to get that from dsquery and then pipe it to dsget:

dsquery user -samid <KerberosID> | dsget user -part <AD Partition> -qused -display


E.g.:
dsquery user -samid adil | dsget user -part dc=xyz,dc=org -qused -display
 display         qused
 Hindistan, Adil 700

dsget succeeded


Note that there is a space between 'xyz users', so we enclosed that part in quotes for cmd to parse it correctly. Otherwise you would get an error:

dsget failed:Value for 'Target object for this command' has incorrect format.


If you however run the last command in PowerShell, it will not work:

PS H:\> dsquery user -samid adil | dsget user -part dc=xyz,dc=org -qused -display
  dsget failed:A referral was returned from the server.
  type dsget /? for help.


To error becomes more meaningful if you omit the dsquery part and just run dsget:

PS H:\> dsget user "CN=adil,OU=xyz Users,DC=xyz,DC=org" -part dc=nyumc,dc=org -qused -display

 dsget failed:'dc=org' is an unknown parameter.

 type dsget /? for help.


So, PowerShell is looking at the part after -part and does not interpret it as cmd.exe would. There are several ways to fix this:

1) Simply use quotes or single quotes around the part after -part
PS H:\> dsget user "CN=adil,OU=xyz Users,DC=xyz,DC=org" -part "dc=nyumc,dc=org" -qused -display


The other option is to tell PowerShell to run cmd.exe and let it parse the rest using call operator:

PS H:\> &cmd.exe /c 'dsget user "CN=adil,OU=xyz Users,DC=xyz,DC=org" -part dc=nyumc,dc=org -qused -display'
Display         qused
Hindistan, Adil 700
dsget succeeded

And here is a little function I wrote to beautify it (yep, I know, beauty is in the eye of the beholder)

function Get-DomainJoinCount {
 param (
          [Parameter(ValueFromPipeline)]
          [Alias('ID','SamID')][string]$KerberosId='adil'
       )
 dsquery user -samid $KerberosID |
 dsget user -part "dc=xyz,dc=org" -qused -display).trim() -replace '\s{2,}',';' | select -skip 1 -First 1 |
 ConvertFrom-Csv -Delimiter ';' -Header "User Name","Count"
}

PS H:\> Get-DomainJoinCount adil |ft -AutoSize

User Name        Count
---------        -----
Hindistan, Adil 700 

2014-01-19

Updating Environment Variables

I installed Python 2.7 on my computer, but install path was not added to environment variables. A couple of quick notes on doing this with PowerShell:

  • PowerShell does not offer a direct cmdlet to manipulate $env:path that would stick after the shell is closed.
  • [System.Environment]::GetEnvironmentVariable() and [System.Environment]::SetEnvironmentVariable() .NET methods are the way to go about this. 
  • Standard PowerShell window would not work in this case, and you need to be on an elevated PowerShell window.
  • You can also directly manipulate the registry key which holds system-wide environments variables [HKLM\System\CurrentControlSet\Control\Session Manager\Environment\Path]

To get the current path, you can either use .NET method or built in $ENV variable:

PS C:\> [System.Environment]::GetEnvironmentVariable('PATH')
C:\Program Files (x86)\AMD APP\bin\x86_64;C:\Program Files (x86)\AMD APP\bin\x86;...

PS C:\> $env:Path
C:\Program Files (x86)\AMD APP\bin\x86_64;C:\Program Files (x86)\AMD APP\bin\x86;...

Note that, with the .NET [System.Environment], you have also the option of getting USER variable only:

PS C:\[System.Environment]::GetEnvironmentVariable('PATH','USER')
C:\Program Files (x86)\Google\google_appengine\

To add my Python Path (c:\Python\27):

PS C:\> [System.Environment]::SetEnvironmentVariable('PATH',$env:Path + ';C:\Python\27', 'MACHINE')


It's important to remember that when you are trying to make system-wide changes, or sometimes even to access system data, you need to be on an elevated shell, which you can get by right clicking PowerShell launcher and choosing 'Run As Administrator'

Most of the time, PowerShell will give you hints that you need to be on an elevated shell but not always. For example, if you run the command above in a regular shell, you do not get an error. In fact, the change will seem to go through, except it won't and when you launch a new shell, you will notice that path you added is not there.

Below is an example where you do get an error if you attempt to run it in a non-elevated shell:

function get-smart {
gwmi -namespace root\wmi -class MSStorageDriver_FailurePredictStatus |
        select InstanceName,PredictFailure,Reason |ft -a
}

Regular shell will 'yell' at you in red:

PS C:> get-smart
gwmi : Access denied 
At line:2 char:2
+     gwmi -namespace root\wmi -class MSStorageDriver_FailurePredictStatus |select In ...
Run the same function in an elevated window:

PS C:\> get-smart

InstanceName                                                              PredictFailure Reason
------------                                                              -------------- ------
SCSI\Disk&Ven_Hitachi&Prod_HDT721010SLA360&Rev_ST6O\5&24ba3dff&0&000000_0          False      0
SCSI\Disk&Ven_&Prod_ST31500341AS\4&5ecf4f&0&020000_0                               False      0

By the way, that function will check the SMART status of your hard drives and tell you whether SMART expects any failure

2014-01-15

Fixing quser access is denied error

Daily PowerShell tips from PowerShell.com are a great way to learn PowerShell tips and tricks. They sent a tip yesterday about 'quser'. If you have been around for a while, you may remember that command. A long long time ago, Citrix helped Microsoft to create 'Terminal Server'. They also created some 'q****' commands.

If you go into your system32 directory and list executables that start with q, you will some others, like qwinsta.exe and qprocess.exe, as well as quser.exe Microsoft kept these executables ever since.

I am familiar with them, because many years ago I had written a perl application to monitor Citrix Servers and these commands came handy at the time.

Anyway, PowerShell tip yesterday was about finding out logged on user:

PS H:\> quser  
USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
  >adil              console             1  Active      none   1/15/2014 11:35 AM PS 

H:\> (quser) -replace '\s{2,}',','|ConvertFrom-Csv
 SERNAME    : >adil
SESSIONNAME : console
ID          : 1
STATE       : Active
IDLE TIME   : none
LOGON TIME  : 1/15/2014 11:35 AM


The first command shows what you would have seen by running the command on the current machine, and Tobias Weltner's tip shows us how to first replace 2 or more spaces with ',' and then, use ConvertFrom-CSV cmdlet to convert string into a reusable PowerShell Object (PSObject).

Today, there was a follow up on the tip to find out who logged on on a remote computer, using /server parameter. One of my colleagues tried it and and reported that it was not working for her.





Error 0x000006BA enumerating sessionnames
Error [1722]: The RPC server is unavailable.


The first line of error suggested that it was a permissions issue, but she was an admin on the remoe windows 7 box.
The second line was telling us that the remote machine was not responding to Remote Procedure Call (RPC).

So, to fix this, we needed to enable RemoteRPC calls in the registry of the remote machine.
PS H:\> invoke-command -computername adil-w7x32vm2 -Command { set-itemproperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' -Name AllowRemoteRPC  -Value 0x1 -Force }

 And once the RemoteRPC is allowed, query goes through without any errors.

PS H:\> (quser /server:adil-w7x32vm2) -replace '\s{2,}',','|ConvertFrom-Csv

  USERNAME    : testuser
  SESSIONNAME : console
  ID          : 3
  STATE       : Active
  IDLE TIME   : none
  LOGON TIME  : 1/15/2014 10:29 AM
 Note that, you migh also use the following command to connect to remote registry and change the setting, in case you cannot use invoke-command:

reg add "\\ComputerName\HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v AllowRemoteRPC /t Reg_Dword /d 0x1 /f

And query to make sure the change took place:

PS H:\> reg query "\\adil-w7x32vm2\HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v AllowRemoteRPC

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server

    AllowRemoteRPC    REG_DWORD    0x1

2014-01-09

Finding and dealing with a special char in PowerShell

Recently I ran into an issue while doing regex on Bios Serial Numbers. A couple of machines returned a special character as their serial number when I ran the following command

(Get-WmiObject Win32_Bios) .SerialNumber

�����

Not sure if it will show up fine on your computer but it looks like a black diamond shape with question mark in it.

For my purpose, I needed to eliminate such computers from my results, so naturally I wanted to be able to match them in my regular expression.

The problem was that I had no idea how to produce it in my regex.
I knew I could have produced any character using this

[char]'CharCodeGoesHere'

But  how do I find the char code? Here is how:

I copied the first '?' character and then pasted it between the single quotes (could have been double quotes but in general if you do not want something to be evaluated, avoid double quotes) below in PowerShell

PS D:\> [int][char]'�'
65533

So I could reproduce the weird character by dropping [int]

PS D:\> [char]65533


This meant that I could now use it in regular expression like this:

PS D:\>(Get-WmiObject Win32_Bios) .SerialNumber -match "$([char]65533)"
True

While we are at it, here are a couple of valid and invalid ways to do this comparison

PS D:\> '�' -match "$([char]65533)"
True

PS D:\> "�" -match [char]65533
True

PS D:\> '�' -match [char]65533
True

PS D:\> '�' -match '[char]65533'  (Included this because single quote some times trip people. PowerShell does not evaluate the stuff inside single quotes.

PS D:\> � -match "$([char]65533)"
� : The term '�' is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ � -match "$([char]65533)"
+ ~
    + CategoryInfo          : ObjectNotFound: (�:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

In the last example above, Left side of -match operator is not enclosed within any quotes and therefore an error is generated. Error is quite self explanatory.