Google

2013-10-16

Permissions to access WMI remotely

Accessing WMI on a remote machine is pretty mundane for System Admins. They do not even think about it, they have Administrator rights, and it works (for the most part).

In my case, I needed to run a script with a service account and connect from one Windows 2008 R2 Server to another. I needed to figure out what permissions that account should have on the server, short of making the service account an admin. How difficult this can be? Well, it proved to be more difficult to find information on this than I thought it would be.

Here is what happens if the service (domain) account I use tries to access remote server, where it does not have any permissions:

PS> gwmi win32_ComputerSystem -ComputerName test.adilhindistan.com
gwmi : Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
At line:1 char:1
+ gwmi win32_ComputerSystem -ComputerName test.adilhindistan.com
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Get-WmiObject], UnauthorizedAccessException
    + FullyQualifiedErrorId : System.UnauthorizedAccessException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
I looked at several resources including venerable StackOverflow, where I have found several responses to this type of questions but none worked for my case.

There are many KB articles on the subject. This dated MSDN article claimed that you need to be admin on the target box. WMI Troubleshooting, Connecting to WMI on a Remote Computer by Using Windows PowerShell,

I asked in PowerShell Community Group chat, where Joel "Jaykul" Bennett pointed me to this article.

All of them helped, but in the end, I had to find my own solution to it:

1) Add the user to 'Distributed DCOM Users' group on the target server
2) Grant WMI permissions to user by following the steps below:

  • Launch compmgmt.msc and connect to target server
  • Right click on Services and Applications > WMI Control and select "Properties"
  • Click Root (CIMV2 did not seem to work but see update below) and then "Security"
  • Add the domain user and click on "Advanced"
  • Double click on user name
  • Change Applies to to "This namespace and subnamespaces"
  • Click on "Remote Enable" checkbox and hit "OK"
See screenshots below.



Note that I only needed read access. If you need to write or execute WMI methods, then additional checkboxes (Execute Methods, * write) will need to be added.

Update 2013-10-17:
I got an e-mail from Colyn, who had helped answering the question on StackOverflow thread asking me if  adding user to "WinRMRemoteWMIUsers__" group would work for me.

I did not have that option because that group did not exist on the server, which had PowerShell version 2. CIM cmdlets became available with PowerShell v3, so I guess that explained why.

However, I had access to another Windows 2008 R2 server with PowerShell3. Sure enough, that server had the group. the group definition says:
"Members of this group can access WMI resources over management protocols (such as WS-Management via the Windows Remote Management service). This applies only to WMI namespaces that grant access to the user."

On target server:
1) I added domain test user to  "WinRMRemoteWMIUsers__".
2) I granted both "Enable Account" & "Remote Enable" access to  Root/CIMV2 namespace

FAILURE:
Get-WMIObject call failed with the same access denied error:

PS D:\> get-wmiobject win32_ComputerSystem -Computer TEST01
gwmi : Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
At line:1 char:1
+ gwmi win32_ComputerSystem -Computer TEST01
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Get-WmiObject], UnauthorizedAccessException
    + FullyQualifiedErrorId : System.UnauthorizedAccessException,Microsoft.PowerShell.Commands.GetWmiObjectCommand

Similarly, Get-CimInstance with DCOM protocol fails as well

PS D:\> $cimoption = New-CimSessionOption -Protocol Dcom
PS D:\> new-cimsession -computername wstwsdcpvm001.nyumc.org -SessionOption $cimoption
new-cimsession : Access is denied.
   + FullyQualifiedErrorId : HRESULT 0x80070005,Microsoft.Management.Infrastructure .CimCmdlets.NewCimSessionCommand


SUCCESS:
But Get-CimInstance cmdlet which uses WS-MAN protocol succeeded:

PS D:\> get-ciminstance -classname win32_ComputerSystem -ComputerName TEST01

Name             PrimaryOwn Domain     TotalPhysi Model     Manufactu PSCompute
                 erName                calMemory            rer       rName
----             ---------- ------     ---------- -----     --------- ---------
TEST01    Windows...   4026064896 VMware... VMware... TEST01...

NO ALIAS PLEASE!
Here is another twist to this story. If I use the dns alias instead of actual hostname, Get-CimInstance too returned an error, but return code is different (0x80070035).

WMI SERVICE GOTCHA
Another note: if you will be playing with the WMI permissions, you may sometimes see unexpected results if you do not restart WMI service (get-service winmgmt | restart-service -force)  after making a change. For example: I wanted to test removing "Remote Enable" permission and see if I would still be able to use Get-CimInstance. To my surprise it worked for a while...until I restarted the service. Then I started to get errors (HRESULT 0x80041004). I had to add back the "Remote Enable" and restart WMI service to get it back up.

Finally, here are a couple more useful links:

2013-10-08

Detecting and Fixing Duplicate SPN

If you search internet, you will find tons of people trying to find an answer why and how a computer trust relationship is broken and as a result Windows rudely refuses to log them in.

The error message: "The security database on the server does not have a computer account for this workstation trust relationship"


Seeing the message, the first suspicion is that something is wrong with the secure channel that the computer uses to communicate with Active Directory.

I logged into the computer using a local account, launched PowerShell to check the status of Secure Channel:

PS> Test-ComputerSecureChannel
True

OK. That is not the issue then! If it had returned "False", I could have used the -repairChannel parameter to fix it (need to run that in PowerShell Admin console).

PS> Test-ComputerSecureChannel -Repair

BTW, good old 'nltest' can be used to test and reset secure channel too, as shown below, but no need to bother when you have PowerShell?

PS H:\> nltest /sc_query:adilhindistan.com
Flags: 30 HAS_IP  HAS_TIMESERV
Trusted DC Name \\MyDCName.adilhindistan.com
Trusted DC Connection Status Status = 0 0x0 NERR_Success
The command completed successfully

Similarly, the following would reset it.

nltest /sc_reset:adilhindistan.com


Looking at Event logs revealed that this was related to an Service Principal Name issue:

WORKSTATION:
Log Name:      System
Source:        Microsoft-Windows-Security-Kerberos
Date:          10/7/2013 3:59:14 PM
Event ID:      3
Task Category: None
Level:         Error
Keywords:      Classic
User:          N/A
Computer:      testcomputer.adilhindistan.com
Description:
A Kerberos Error Message was received:
on logon session 
 Client Time: 
 Server Time: 20:59:14.0000 10/7/2013 Z
Error Code: 0x7  KDC_ERR_S_PRINCIPAL_UNKNOWN
Extended Error: 0xc0000035 KLIN(0) <===== STATUS_OBJECT_NAME_COLLISION (Object Name Already Exists)
Client Realm: 
 Client Name: 
 Server Realm: adilhindistan.com
Server Name: host/testcomputer.adilhindistan.com
Target Name: host/testcomputer.adilhindistan.com@adilhindistan.com
Error Text: 
 File: 9
Line: f09
Error Data is in record data.


DOMAIN CONTROLLER:
Log Name:      System
Source:        Microsoft-Windows-Kerberos-Key-Distribution-Center
Date:          10/7/2013 3:59:14 PM
Event ID:      11
Task Category: None
Level:         Error
Keywords:      Classic
User:          N/A
Computer:      mydc.adilhindistan.com
Description:
The KDC encountered duplicate names while processing a Kerberos authentication request. The duplicate name is host/testcomputer.adilhindistan.com (of type DS_SERVICE_PRINCIPAL_NAME). This may result in authentication failures or downgrades to NTLM. In order to prevent this from occuring remove the duplicate entries for host/testcomputer.adilhindistan.com in Active Directory.

I ran setspn command to show me the duplicate SPNs:

setspn.exe -X -P

Looked at results, yet the computername I was concerned was not listed. By the way, there is a detailed Microsoft article on SPN and setspn.exe usage here.

setspn.exe -Q HOST/testcomputer

Checking domain DC=adilhindistan,DC=com
CN=testcomputer,OU=Workstations,DC=adilhindistan,DC=com
        TERMSRV/testcomputer.adilhindistan.com
        WSMAN/testcomputer.adilhindistan.com
        RestrictedKrbHost/testcomputer.adilhindistan.com
        HOST/testcomputer.adilhindistan.com
        TERMSRV/testcomputer
        WSMAN/testcomputer
        RestrictedKrbHost/testcomputer
        HOST/testcomputer

Existing SPN found!

That showed me the current SPN and it looked right but did not help with detecting the computer that's causing the conflict. This did the trick:

setspn.exe -Q HOST/testcomputer.adilhindistan.com

Checking domain DC=adilhindistan,DC=com
CN=testcomputer1,OU=Workstations,DC=adilhindistan,DC=com
        HOST/testcomputer.adilhindistan.com
        HOST/testcomputer1

Checking domain DC=adilhindistan,DC=com
CN=testcomputer,OU=Workstations,DC=adilhindistan,DC=com
        TERMSRV/testcomputer.adilhindistan.com
        WSMAN/testcomputer.adilhindistan.com
        RestrictedKrbHost/testcomputer.adilhindistan.com
        HOST/testcomputer.adilhindistan.com
        TERMSRV/testcomputer
        WSMAN/testcomputer
        RestrictedKrbHost/testcomputer
        HOST/testcomputer

Existing SPN found!

So, 'testcomputer1' was incorrectly claiming the SPN HOST/testcomputer.adilhindistan.com and causing the issue.

While on the subject here is another link that might help in other relevant cases:
Kerberos Error Code: 0x7 KDC_ERR_S_PRINCIPAL_UNKNOWN

2013-10-05

The server is unwilling to process the request

I was working on a script that reads the information about printers on a print server and creates AD groups for each share name if it does not exist. Script is part of a process to manage shared printers for users.

So, the part of code goes like this:

try {
    New-AdGroup -Path "OU=Printers,OU=Groups,DC=XYZ,dc=org" -Name $PrinterGroupName -GroupScope DomainLocal -GroupCategory Security -Description $Description -OtherAttributes @{Info=$Comment}
    }
catch { $_ }

There is a line above that code to get all printers on the Print Server:
 Get-Printer -ComputerName $PrintServer   ## Available on Win8+
 

Then this code retrieves Printer Group Name, Printer Location as Description 'AND' a comment field out of 'Get-Printer' cmdlet results and populates relevant fields in the new group.

This code was failing to create some groups and the error message was quite unhelpful: "The server is unwilling to process the request"

I looked at the groups and realized that New-ADGroup was not happy when $comment did not include anything.

So, I added a couple of lines before try/catch block to make sure I would have something for both Active Directory Group's Description and  Notes fields to fix the issue:

$Description = $Comment = $printer.name

if ($printer.location) {

   $Description = $printer.location

}

If ($Printer.Comment) {

  $Comment = $Printer.Comment

}

2013-09-27

Issue with PowerShell 4 AD Module on Windows 8.1

Just a quick note that I am seeing some issue with the new PowerShell 4.0 ActiveDirectory Module on Windows 8.1 when retrieving user properties.

PS> get-aduser adil -properties *

get-aduser : One or more properties are invalid.

Parameter name: msDS-AssignedAuthNPolicy

At line:1 char:1

+ get-aduser adil -properties *

+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    + CategoryInfo          : InvalidArgument: (hindia01:ADUser) [Get-ADUser], ArgumentException

    + FullyQualifiedErrorId : ActiveDirectoryCmdlet:System.ArgumentException,Microsoft.ActiveDirectory.Management.Commands.GetADUser

The property 'msDS-AssignedAuthNPolicy' it is complaining about seems to be a new one implemented on Windows Server 2012 R2, according to MSDN: http://msdn.microsoft.com/en-us/library/dn366549.aspx
You can specify the properties you want manually, except that one of course. Hopefully, Microsoft will fix it by the time Windows 8.1 GA on October 18, 2013.

Update 2014-01-09:
I kinda forgot about this issue but it's still not fixed. You can workaround this issue by piping the user object to Get-ADObject cmdlet:

Get-ADUser Adil | Get-ADObject -properties *



By the way, Get-ADComputer cmdlet has the same issue and same workaround can be applied to it.
There is also a bug filed for it here: https://connect.microsoft.com/PowerShell/feedback/details/806452/windows-8-1-powershell-4-0-get-adcomputer-properties-bug#

Update 2014-03-15
Fixed: http://support.microsoft.com/kb/2923122

Update 2014-03-17
Unfortunately the fix does not seem to work for me. I installed the update rollup March 2014 via WSUS so I already had i for a few days and file information is listed here: http://support.microsoft.com/kb/2928680

I have these files (see the screenshot below) yet I still get the same error as before.


2013-09-02

PowerShell try catch gotcha

I check out StackOverflow's PowerShell section time to time, and one issue seems to trip people a lot: Try/Catch block.

Here is how Try/Catch is block is constructed:


Try {
## Some code here which is likely to fail
}
Catch {
## What to do if try block has a "terminating" error
}

Here is an example:


try { get-content c:\a.txt;"file exists" }catch{"that file does not exist"}

If you run the code above; it will throw an error on the Get-Content if 'c:\a.txt' file does not exist. And in theory we should be able to catch that but that is not exactly what happens:


PS Z:\> try { get-content c:\a.txt;"file exists" }catch{"that file does not exist"}
get-content : Cannot find path 'C:\a.txt' because it does not exist.
At line:1 char:7
+ try { get-content c:\a.txt;"file exists" }catch{"that file does not exist"}
+       ~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\a.txt:String) [Get-Content], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand

file exist
PS Z:\>

Did you notice that "file exist" line being printed? Umm, what happened?

Well, remember I said "terminating" errors are caught. the first line is an error but not a terminating error. And therefore, the next line is executed.

Anything we can do to stop this? Yes, use -ErrorAction after the command or $ErrorActionPreference.


PS Z:\> try { get-content c:\a.txt -ErrorAction stop; "file exists" }catch{"that file does not exist"}
that file does not exist
PS Z:\>

With ErrorAction set to STOP, the error on the first line becomes a terminating error and hence get caught in the CATCH {} block.

Similarly, we can set the $ErrorActionPreference variable before the line top "STOP"; and tell our code to treat any error as terminating one.


PS Z:\> $ErrorActionPreference="STOP"; try { get-content c:\a.txt; "file exists" }catch{"that file does not exist"}
that file does not exist
PS Z:\>

If not specified, $ErrorActionPreference is set to "Continue".


PS Z:\> $ErrorActionPreference
Continue
PS Z:\>

Note that it is not "SilentlyContinue", which would execute the next statement without complaining about the error.


PS Z:\> try { get-content c:\a.txt -ErrorAction SilentlyContinue; "file exists" }catch{"that file does not exist"}
file exist

This is equivalent to "On Error Resume Next" in vbScript.

2013-08-13

Work around quirky Printui.dll on XP

In the previous post, I talked about issues I was seeing when using printui.dll to push per-machine connections on Windows XP. I did tons of testing and in the realized that XP's spooler service was too flaky for my purpose.

I spent some time diving deeper into what the commands are doing and if there was anything that I could do in the startup script that would make it more consistent...

Let's cut to the chase:

I concluded that the most reliable way to make sure printers will be pushed when users logged in was to make sure relevant connection entries are created by my script under HKLM during startup, instead of relying printui.dll /ga switch.

How did I arrive at this conclusion?

Well,  I used Process Monitor (aka Procmon) from SysInternals,
watched what happens if I issue commands like the ones below manually

  • rundll32 printui.dll,PrintUIEntry /ga /n\\PrintServer\Printer (add per-machine printer connection)
  • rundll32 printui.dll,PrintUIEntry /gd /n\\PrintServer\Printer (remove per-machine printer connection)

A Watched sequence of events during boot (Procmon > Options > Enable Boot-Logging)

Here are a few things to note that will help us understand what is going on :

  • Our Print Server is named PrintServer1 
  • Printers shared on it has sharenames Printer1.. PrinterN 
  • HKLM Connections key: HKLM\SYSTEM\CurrentControlSet\Control\Print\Connections
  • HKCU Connections key: HKCU\Printers\Connections
  • If there is no ",,PrintServer1,PrinterX" entry under HKLM Connections key, user does not get the PrinterX connection.

Q. What happens if we "add per-machine printer connection" in a user session?
A. Seemingly nothing, user does not see the new printer connections until Spooler service is started. In reality, a new registry key under HKLM Connections key is created.

Q. So, what happens if we re-start the spooler services?
A. Normally, user gets the printer connection. It does not show up immediately, spooler service takes its sweet time but unless something goes terribly wrong, new printers shows up in a minute or two. Here is how (shortened for relevancy):

Spooler Service checks the entries under HKLM Connections key and detects a new printer connection, which will be in the following form:
HKLM\SYSTEM\CurrentControlSet\Control\Print\Connections\,,PrintServer1,PrinterX
Server=\\PrintServer1 (RegSZ)
Provider=Win32spl.dll (RegSZ)
LocalConnection=0x0000001 (DWORD)

You probably noticed that \\PrintServer1\PrinterX has been transformed to ,,PrintServer1,PrinterX and became a key. Provider and LocalConnection always had the same value, "Server" was set to the name of the Print Server with '\\' prefix.

So far, so good. The rest is the tiring loop:

  • Spooler starts going through SIDS
  • Creates a new connection under HKCU\Printers\Connections,,PrinterServer1.PrinterX
  • Clones the 'Server', 'Provider', 'LocalConnection' settings from HKLM Connections key
  • Queries and creates a new port setting:

    HKLM\Software\Microsoft\Windows NT\CurrentVersion\Ports\NeXX: (Reg_SZ ),
    where Ne in my case went from Ne00 to Ne08
  • It then created an entry under devices that linked the port to printer:

    HKCU\Software\Microsoft\Windows NT\CurrentVersion\Devices\
    \\PrintServer1\PrinterX = Winspool,NeXX: (Reg_SZ)
  • In the next step, spooler creater a PrinterPort entry in the following form:

    HKCU\Software\Microsoft\Windows NT\CurrentVersion\PrinterPorts\
    \\PrintServer1\PrinterX = Winspool,NeXX:15,45 (Reg_SZ)
  • In the actual flow, HKCU was the last. Spooler actually started with SID S-1-5-19 (HKU\S-1-5-19\)and cycled through all SIDS under HKU but you get the point.
  • THEN, an important step comes: querying printer, getting a connection to it (I see a %windir%\System32\spool\PIPE file created), creating a registry key for printer and writing everything about that printer

    HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Providers\LanMan Print Services\Servers\PrinterServer1\Printers\PrinterX
So, that's gist of what spooler seems to be doing. 

What's the problem?

Well, the problem I identified was that during the startup Spooler service would something be slacking. The command I ran to add a per-machine printer connection would return 258. 
Whenever rundll32 returned 0, that process ended up creating an HKLM connection entry and printer showed up just fine for user.
If, however, rundll32 returned 258, no connection was created.

So, what does return code 258 mean? Answer comes from Alan Morris of Microsoft Windows Printing team:

"this is the error
C:\>winerror 258
   258 WAIT_TIMEOUT <--> No NTSTATUS matched
printui does not validate the data that you are adding to the registry."
He goes on to explain:

"I'd check the number of printers and drivers as well as the drivers installed on a machine where this is failing.  The spooler may not be fully online and printui does call into spooler for these methods." 

Well, all good, but I had seen services.exe kicking off spooler service a full minute before any commands were issued to it. Additionally, spooler service would almost always map the first printer but the number of printers it connected was varying each time.

I was using the same XP VM and got considerably different results (sometimes tons of return code 258 errors, sometimes just 1 or two).

Techs in the field were reporting a similar pattern and a quick  Google search also got many hits in terms of people complaining about success rate of using these commands in the startup scripts.

What do we do now?

Well, as I said in the beginning, my approach was to help the spooler service a bit by creating the HKLM connections registry keys for it. In my tests, it worked like a charm (YMMV) every single time.

Below is modified sample script


on error resume next
Const ForReading = 1, ForWriting = 2, ForAppending = 8, CanCreate=True
ScriptVersion="2013-08-13-02"

Set oShell = CreateObject("WScript.Shell")
oShell.LogEvent 0, "WinXP_WSStartup.vbs: Script Version: " & ScriptVersion

'Get group membership for the machine
Set objSysInfo = CreateObject("ADSystemInfo")
Set objComputer = GetObject("LDAP://" & objSysInfo.ComputerName)

Groups = objComputer.GetEx("MemberOf")
' If Above Command does not find any groups for computer, it will error
if  Err.Number <> 0 Then  
 oShell.LogEvent 0, "WinXP_WSStartup.vbs: Computer is not member of any AD groups. Exiting."
 Wscript.Quit
end if

isFirst=vbTrue
Count=0
For Each group In Groups
  group=Replace(group,"/","\/")   ' escape groups with a slash in the object name   
  Set objGroup = GetObject("LDAP://" & group)
  If ( Instr(LCase(objGroup.CN),"grp.prn." ) > 0 ) then          
  If isFirst Then
   PrinterGroups = objGroup.CN
   isFirst=vbFalse
   Count=1
  Else
   PrinterGroups = PrinterGroups & ";" & objGroup.CN
   Count=Count+1
  End If   
  End if 
  'AllGroups = AllGroups & ";" & objGroup.CN
Next 

if (Count > 0) Then
   
 'Take care of setting up Reg File
 Set oFSO = CreateObject("Scripting.FileSystemObject") 
 sRegFile = oShell.ExpandEnvironmentStrings("%TEMP%")  & "\WinXP_WSStartup_PrinterConnections.reg" 
 If oFSO.FileExists(sRegFile) Then
  oFSO.DeleteFile(sRegFile)
 End If 
  
 Set oRegFile = oFSO.OpenTextFile(sRegFile, ForAppending, CanCreate)
 oRegFile.WriteLine "Windows Registry Editor Version 5.00" & vbCrLF & vbCrLF
  
 ' Now let's process each printer grou[
 
 arrPrinterGroups=Split(PrinterGroups,";") 
 for each PrinterGroup in arrPrinterGroups 
  arrGroup = Split(PrinterGroup,".")
  ' skipping first 2 grp and prn as groups are named grp.prn.ServerName.PrinterName
  Server=arrGroup(2)
  Printer=arrGroup(3)
  
  '' Update registry file
   oRegFile.WriteLine("[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Connections\,," & Server & "," & Printer & "]" )
   oRegFile.WriteLine(chr(34) & "Server" & chr(34) & "=" & chr(34) & "\\\\" & Server & chr(34))
   oRegFile.WriteLine(chr(34) & "Provider" & chr(34) & "=" & chr(34) & "win32spl.dll" & chr(34))
   oRegFile.WriteLine(chr(34) & "LocalConnection" & chr(34) & "=dword:0000001" & vbCrLf)

  ''
'  cmdConnectPrinter="%comspec% /c rundll32 printui.dll,PrintUIEntry /ga /n\\" & Server & "\" & Printer
  oShell.LogEvent 0, "WinXP_WSStartup.vbs: Connecting to Printer: " & Printer & " on Server: " & Server
'  oShell.LogEvent 0, "WinXP_WSStartup.vbs: Running " & cmdConnectPrinter
  
  ' Hide Window and return immediately without waiting
'  oShell.Run cmdConnectPrinter,0,false
   
 Next 
 
 oRegFile.close
 
 ' Import Printer Connection file silently
 oShell.Run "regedit /s " & Chr(34) & sRegFile & Chr(34), 0, True 
 
End If

oShell.LogEvent 0, "WinXP_WSStartup.vbs: Nothing else to do. Exiting" 
Set oShell=Nothing

2013-08-10

Printer Connection Issues on XP

I've been hitting my head against the wall with the inconsistencies I see when trying to use Microsoft printui.dll to connect network printers on XP machines. Here goes the story...

Problem:

There are a bunch of "floor" printers that are published in a Windows 2008 R2 print Queue. We want every user who logs on to the computers on that floor to automatically get these printers.

Solution:

There are several well known ways, I won't go into details. Here is a nice blog that goes over them: http://blog.powershell.no/category/print-management/

In my case, I came up with a naming schema for printer groups

  • grp.prn.PrintServerName.PrinterShareName 

ComputerNames are added into these groups, which I then parse through a startup script and use well-known printui.dll to connect the machine to these printers. There were several technical but of course also some political reasons to do it this way. Your mileage may vary.

Windows 7 & PowerShell

Anyway, I wrote a Computer Startup PowerShell script that is deployed via Group Policy, put the logic in to detect which printers the computer should be connecting to, and wrapped it around the command below:

  • printui.exe /ga /n\\PrintServer\Printer

and all went well.

The switch (ga) is causes "per-machine" connection. Underneath, it caused creation of a registry key entries under HKLM and once users are logged in it "pushes" these connections to user, so that user is able to see and print to those printers without requiring them do anything.

Windows XP, still around...

XP is another story. Well, for one, xp does not have printui.exe but its older cousin rundll32 printui.dll,PrintUIEntry, which is well documented on technet, is still around and unfortunately, there are several XP machines without PowerShell in the environment, so I had to go back to vbscript.

It's a similar story, I back-ported the logic and script to vbscript:

  • rundll32 printui.dll,PrintUIEntry /ga /n\\PrintServer\PrinterName 

Note that, technically, it's not the printer name we use but the Printer Share Name, which may be different than the printer name). Tested it, yep works.

A couple of days later, I hear from one of the techs that printers are not getting mapped for his computers. Script logging clearly showed that it ran the command. Problem is, command does not have a return code to tell us whether command was successful or not. I could not find any documentation that explains the details of what goes on at that point.

Perhaps a debug flag somewhere would be useful but spooler is a very magical and at the same time finicky service especially on Windows XP. One thing is certain, it is inconsistent and there is no way to know why (yet)...

I hit other weird issues during troubleshooting. Below is one of them and the workaround.

Issue:

I wanted to remove some of the printers I connected to. Because these printers are connected per-machine using /ga switch, they need to be removed using /gd switch.

So, I run the appropriate command

  • Locally: rundll32 printui.dll,PrintUIEntry /gd /n\\PrintServer\PrinterName
  • Remotely: rundll32 printui.dll,PrintUIEntry /gd /c\\TargetComputer /n\\PrintServer\PrinterName


Printer connection cannot be removed. Operation could not be completed.

Workaround:

The fastest workaround I found for this issue is delete the relevant registry keys that are under:

  • HKLM\System\CurrentControlSet\Control\Printer\Connections\


Below are some of the other useful resources on Printing


Sample VBScript:

On error resume next
ScriptVersion="2013-08-09-02"
Set oShell = CreateObject("WScript.Shell")
oShell.LogEvent 0, "WinXP_WSStartup.vbs: Script Version: " & ScriptVersion

Set objSysInfo = CreateObject("ADSystemInfo")
Set objComputer = GetObject("LDAP://" & objSysInfo.ComputerName)

Groups = objComputer.GetEx("MemberOf")
' If Above Command does not find any groups for computer, it will error
if Err.Number <> 0 Then
oShell.LogEvent 0, "WinXP_WSStartup.vbs: Computer is not member of any AD groups. Exiting."
Wscript.Quit
end if

isFirst=vbTrue
Count=0
For Each group In Groups
group=Replace(group,"/","\/") ' escape groups with a slash in the object name
Set objGroup = GetObject("LDAP://" & group)
If ( Instr(LCase(objGroup.CN),"grp.prn." ) > 0 ) then
If isFirst Then
PrinterGroups = objGroup.CN
isFirst=vbFalse
Count=1
Else
PrinterGroups = PrinterGroups & ";" & objGroup.CN
Count=Count+1
End If
End if
Next

if (Count > 0) Then
arrPrinterGroups=Split(PrinterGroups,";")
for each PrinterGroup in arrPrinterGroups
arrGroup = Split(PrinterGroup,".")
' skipping first 2 grp and prn as groups are named grp.prn.ServerName.PrinterName
Server=arrGroup(2)
Printer=arrGroup(3)

cmdConnectPrinter="cmd /c rundll32 printui.dll,PrintUIEntry /ga /n\\" & Server & "\" & Printer
oShell.LogEvent 0, "WinXP_WSStartup.vbs: Connecting to Printer: " & Printer & " on Server: " & Server
oShell.LogEvent 0, "WinXP_WSStartup.vbs: Running " & cmdConnectPrinter

' Hide Window and return immediately without waiting
oShell.Run cmdConnectPrinter,0,false

Next
End If

oShell.LogEvent 0, "WinXP_WSStartup.vbs: Nothing else to do. Exiting"
Set oShell=Nothing

*******
In PowerShell, I wrote a couple of functions, thinking I might use the same logic to add user-side printer connections, hence the 'user' & 'computer' objectCategories below.

A single line calls the functions:


Connect-NetworkPrinter -objectCategory computer


Here are the functions:


Function Connect-NetworkPrinter {
[CMDLETBINDING()]
  Param(    
    [ValidateSet('user','computer')]
    [String]$objectCategory='computer',

[string]$PrinterGroupIdentifier='^cn=grp.prn.',
[string]$name )
if (!$name) { if ($objectCategory -eq 'computer') { $name=$($env:COMPUTERNAME) } if ($objectCategory -eq 'user') { $name=$($env:USERNAME) } } Write-Verbose "Connect-NetworkPrinter: PrinterGroupIdentifier set to $PrinterGroupIdentifier" Write-Verbose 'Connect-NetworkPrinter: Calling Function to Get Group Membership'
$groups = Get-ADGroupMembership -objectCategory $objectCategory -name $name
if (!$groups) { Write-Verbose "Connect-NetworkPrinter: No groups returned for current computer" logMsg "Connect-NetworkPrinter: No groups returned for current computer" return $false }
$printergroups=@() foreach ($group in $groups) {
Write-Verbose "Connect-NetworkPrinter: Checking group $group" if ($group -match $PrinterGroupIdentifier) { Write-Verbose "Connect-NetworkPrinter: Matches criteria $group"
$Server,$Printer,$null=($group -replace $PrinterGroupIdentifier -replace ',ou=.*$').split('.')
Write-Verbose "Connect-NetworkPrinter: Connecting to Printer: $Printer on Server: $Server" try { Invoke-Expression 'rundll32 printui.dll,PrintUIEntry /ga /n"\\${Server}\${Printer}"' ## rundll32 does not return anything so cannot check on error logMsg "Connecting to Printer: $Printer on Server: $Server" } catch { Write-Error "An error occured $_" logMsg "Error occured connecting to Printer: $Printer on Server: $Server" -entrytype 'Error' }
# [PSCustomObject]@{Server=$server;printer=$printer} ## return PSCustomObject } } Write-Verbose 'Connect-NYUPrinter: Exiting' logMsg "Printer connections complete"
}
function Get-ADGroupMembership { <# .SYNOPSIS Gets Active Directory Group membership for users or computers
.EXAMPLE Get-ADGroupMembership -name 'user1','user2' -Verbose Displays group membership for users user1 and user2
.Parameter name Name of the computers or users
.Parameter ObjectCategory Can be user or computer. By default it is 'computer'
.NOTES Output is string array of Distinguished Names. If nothing is found, it returns $false #>
[cmdletbinding()] param([Parameter(Mandatory=$true)] [string[]]$name, [ValidateSet('user','computer')] [String]$objectCategory='computer' ) Begin { Write-Verbose 'Get-ADGroupMembership: Creating Directory Search Object' $searcher=New-Object DirectoryServices.DirectorySearcher }
Process { $name | % { Write-Verbose "Get-NYUGroupMembership: Processing $_" $filter="(&(objectCategory=$objectCategory)(cn=$_))" ## this will only find user object $searcher.PageSize=1000 $searcher.Filter=$filter $result=$searcher.FindOne()
if ($result) { $result.Properties.Item("memberOf") } else { Write-Verbose '...is not member of any groups' } } }
END { Write-Verbose 'Get-ADGroupMembership: Disposing Directory Search Object' $searcher.Dispose() } }
I am not posting here the 'logmsg' function that the PowerShell functions are referring to, but it is basically just logging events into a Special Log I set up for all my scripts. It's so old, I did not even bother to name it PowerShell-way.

Enjoy!

2013-06-10

How to install iOS7 Beta on iPhone 5

I have captured some screenshots but do not have time to upload them yet. Upgrade process is pretty much the same as before:


  • Download iOS 7 Beta from developer.apple.com > iOS Downloads (You need to have iOS Developer Account with Apple to get there) Protip: Use Safari on Mac, if possible. For whatever reason, when I used Chrome on my mac, it would just direct me to 'maintenance' page, which said 'We'll be back soon'.
  • Open up the downloaded dmg and extract the .ipsw file on to your desktop
  • Launch iTunes & Connect your iPhone (Supported models iPhone 4, 4S, 5)
  • Click on  your device name and make sure to back it up (on iCloud)
  • You can also do a backup on your iPhone using Settings > iCloud menu.
  • Once done, click Option Key (Alt key if you are using PC keyboard) and click Restore on iTunes
  • On the File Picker window, click the Desktop on the left and choose the extracted iOS7 file
  • All in all this takes about 5mins.
  • Once completed, it will try to activate. Your phone needs to be registered in the iOS Dev Portal.







Update 2013-06-19:

After using iOS7 for a week, I rolled back. I worked out most of the annoyances but Google+ kept on crashing and I do use Automatic Backups for my pics. So, I can't wait for months to get it all sorted out.

Here are a couple of notes:

  • Updates did not work always. Your would see them but clicking update would not do anything.
  • Although I backed up everything to iCloud, most of the settings for apps did not come back I had to enter username/pwd for many apps as if just installed.
  • Google Authenticator was one of the apps where I lost data and I had to move all 2 factor authentication accounts using 'moving to new iphone' menu. Facebook two-factor authentication is easy to move as well.


2013-04-01

Change Location Settings via PowerShell

Today, I have seen a question on StackOverflow.com about how to turn on/off Location Settings in Windows 8 via PowerShell.

This setting is available in the Control Panel (Win + I > Control Panel), under "Location Settings".


To be able to locate the relevant registry key, I launched Procmon (aka. Process Monitor) from SysInternals. Anyone who used the application knows how overwhelming the data will be in just a few seconds of capture. So I had to be quick:

  • I started the capture, 
  • immediately cleared the checkbox next to "Turn on the Windows Location Platform", 
  • clicked Apply
  • And stopped Capture

Even though all that took a few secs, capture generated tens of thousands of events. I started from bottom, excluding all the irrelevant Operations, until I reached the one I am looking for "RegSetValue"





The path it referred to sounded promising:
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Sensor\Overrides\{GUID}\SensorPermissionState

I checked another Windows 8 machine and confirmed that the GUID was the same:
Then, manually changed registry key from 0 to 1 and refresh Location Setting to confirm checkbox was back on.


Once that was done, doing it in PowerShell was quite trivial:

To turn on the Windows Location Platform set value to 1:


set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Sensor\Overrides\{BFA794E4-F964-4FDB-90F6-51056BFE4B44}' -Name SensorPermissionState -Value 0x1

To turn off the Windows Location Platform set value to 0:


set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Sensor\Overrides\{BFA794E4-F964-4FDB-90F6-51056BFE4B44}' -Name SensorPermissionState -Value 0x0

2013-01-23

Workaround to get VMWare boot from USB

Plop Boot Manager Menu
This is probably not news to most VMWare users: Even with the latest VMWare Workstation 9 version, there is still no support from VMWare to boot from a USB drive.

The workaround most people use is to download Plop Boot Manager from 'http://www.plop.at/en/bootmanager/download.html', which acts like a bootable CD and gives you a list of menu options to boot from, one of which is 'USB' (see the screenshot).

I must admit that compared to Live CD, .iso boot options, USB booting is less common but you would think the most advanced solution in this space can do that...

2013-01-13

First Few Days with Windows 8

On the first (work) day of the new year, I decided to upgrade my primary work machine to Windows 8. I was not however ready to be 'down' in case things did not work out well, so I did a fresh install on the second drive.

GUI

OK, what's all this fuss about Windows 8 being Vista-2? Reading about lots of complains and all the talk about steep learning curve etc. I was expecting to be frustrated. Instead, I felt quite at home. All the usual keyboard bindings I am used to worked, I was able to quickly find any application I needed to run. Similarly it felt natural to locate power settings to tell windows not to shut itself down when I am not using it.

To be fair, I might have been frustrated if I was put in front of a Windows 8 box without knowing a single thing about it. I knew a couple of things like 'Charm' bar,  how to 'close an application' via mouse gesture,  but that's pretty much it.

I guess Microsoft could have done a better job of training people during set up or by providing some sort of welcome wizard showing people how to accomplish most common tasks to get people going.

APPS & WINDOWS APPSTORE

The first thing I did was to visit Windows AppStore to see what's available. There are bunch of 'apps' in there, though not much really. It felt more like Chrome Apps, which are 'mostly' links to existing web pages. I always like to use keyboard shortcuts, so I installed a couple of apps like "Windows 8 Shortcuts",  I installed.

...

Well, things got in the way and I could not get to finish this blog post as I intended to. I've been using Windows 8 extensively and have a much better notion of good and bad now. But before I list any of them, I would suggest anyone reading this to stop here and take a look at Paul Allen's Windows 8 review. It covers pretty much everything I wanted to touch upon if I had finished this post in the first few days as I initially aimed.

Assuming you've read that review. Here are a few additional notes from me...

WINDOWS DEFENDER

As described here by Microsoft, Windows Defender is essentially the Antivirus previously included in Windows Essentials. Like Apple's XProtect, Microsoft built this feature into the OS.

You can access it by searching it in Applications: Ctrl+Q > Windows Defender

I happen to keep a folder full of malware. When I download any anti-virus app to see how effective it is, one of the things I do is visit that folder and see if real-time protection will detect them. Because I am not really launching any of the files, some of that do not detect them that way. I won't go into details/

Then, I run a full disk scan and check the results. Comodo, for example had detected a file in a different folder that none-other had detected before. I uploaded that file to VirusTotal.com, which runs many common  anti-virus engines and reports the results. Not surprisingly, Comodo was the only AV that reported it: a false positive.

Anyway, Windows Defender detected all of them successfully when I did a full scan, which is good because I had replaced Microsoft Essentials a year ago as it had failed to detect any.

Yet, I may still need to disable Windows Defender and go with a different solution, not because of its detection rate but because I am suspecting that it has a bug that causes Windows Explorer to crash.

MYSTERIOUS WINDOWS EXPLORER CRASHES

After doing an in-place upgrade from Windows 7, I did not notice any issues for a couple of days but then started to notice that sometimes taskbar would just disappear and re-appearing. I know from experience that this happens if Windows Explorer crashes.



When I checked the Event Viewer, sure enough I saw Event ID 1000 Errors in the Application log and a couple of "Information" entries afterwards that told me "The shell stopped unexpectedly and explorer.exe was restarted".

Slowly, I detected a pattern and was able to repro the issue: It would crash when I browsed to some folders where I had tons of files. Antivirus is a suspect in such situations... To see if Windows Defender may be the culprit,

  • I browsed to the problem folder and scrolled down quickly. Explorer crashed.
  • Repeated this a few times and Windows Explorer kept on crashing each time.
  • Then I brought up Windows Defender > Settings > Real-time protection, and Turned it off.
  • Repeated the test above. This time Windows Explorer did not crash.

I am not fully convinced that Windows Defender is causing the Windows Explorer crash yet but it is the primary suspect at this point. I still need to do more testing and possibly take a crash dump to see if there any clues there.

DUAL-SCREEN CHARMING WOES

I have two monitors attached to my machine. One of the features of Windows 8 was native support for multi-monitor situations, yet I am not sure I like the implementation.

My first "annoyance" is with bringing up Windows Charm Bar (Win + C), which is supposed to show up when I am point at the upper right corner edge of the screen. That works, but it is not smooth. Sometimes, mouse will jump into next screen and I have to pay special attention to where exactly I am pointing it to.

The second "annoyance" is about the taskbar, it is mirrored on each screen. It seems redundant to me but I could not yet find an option to tell Windows 8 to display it only on one screen.

SHORTCUTS


You can help yourself a lot by learning a couple of shortcuts. There are tons of lists, tips and tricks out there you can find on the net but here are just the few to get you started. Even, if you insist on not learning anything else, learn these...

On your keyboard:
Windows Key  = Win
Control Key = Ctrl
Alt Key = Alt

Shortcuts:
Win      ->  Simply pressing Windows key let you switch between desktop and Start screen
Win + E -> Windows Explorer
Win + C -> Charms Bar
Win + I -> Windows Settings
Win + D -> Windows Desktop
Win + Q -> Search installed apps
Win + X -> Menu (see screenshot below)

Alt + F4 -> Closes active window, keep doing it and you can even restart windows.

"Win + X" shortcut displays a menu at the bottom left corner of Desktop, where "Start" menu used to be.

The bottom option will take you to Desktop as well.









FINAL THOUGHTS
I personally felt it was well worth spending $40 to get this upgrade. Microsoft is here to stay with us for quite some time and so is Windows. Adapting to new environment is a "built-in feature" of human beings as is resisting to change yet as we learned from Star Trek "resistance is futile" (extra points if you are playing Ingress and you are 'Englightened').

I am sure Microsoft will keep on ironing out pain points for users and make the UI better but no, Metro is not a disaster and as far as I can tell, Windows 8 is an OK release so far.

2013-01-11

Getting Members of Large Groups via PowerShell


PowerShell AD module make checking group membership a trivial task. If I wanted to get the list of members for a group, I can use Get-ADGroupMember cmdlet. Problem with module is that, it's an 'extra' component on top of standard installation and if you will use it, you need to make sure that your script will have access to it. You may be using PowerShell as your logon/start-up script and your machines may not have the AD module. Yes, there are techniques remote-session techniques that can be employed to work around this but most would prefer to enumerate group members some other way.

One common method is using .Net classes, specifically "DirectoryServices.DirectorySearcher". I had a function that was using this class to enumerate group members, something like this:

function Get-GroupMembers {

  param ([string]$group)

  $searcher=new-object directoryservices.directorysearcher   
  $filter="(&(objectClass=group)(cn=${group}))"
  $searcher.PageSize=1000
  $searcher.Filter=$filter
  $result=$searcher.FindOne()
  $members = $result.properties.item("member")
  if ($members) {
    return $members
  }

  return $false

}


I noticed that this stopped working for a specific group, and when I manually looked into the group properties,  I found that "member" property of the group was empty. There was; however, another noticeable property that got populated instead: "member;range=0-1499".

Long story short, when members of a group exceed 1500, AD was populating that property instead of plain 'member' property and I needed to use a different technique called 'ranged retrieval'.

The main change is that we use 'PropertiesToLoad' property to feed $searcher object the range of members we would like to get and loop through them. When we give $searcher object an invalid range, it throws an error at us and we catch that to exit the loop. Here is the modified function.

## Return members of given AD group

function  Get-GroupMembers {

    param ([string]$group)

    if (-not ($group)) { return $false }
  

    $searcher=new-object directoryservices.directorysearcher   
    $filter="(&(objectClass=group)(cn=${group}))"
    $searcher.PageSize=1000
    $searcher.Filter=$filter
    $result=$searcher.FindOne()

    if ($result) {
        $members = $result.properties.item("member")

        ## Either group is empty or has 1500+ members
        if($members.count -eq 0) {                       

            $retrievedAllMembers=$false           
            $rangeBottom =0
            $rangeTop= 0

            while (! $retrievedAllMembers) {

                $rangeTop=$rangeBottom + 1499               

               ##this is how it would show up in AD
                $memberRange="member;range=$rangeBottom-$rangeTop"  

                $searcher.PropertiesToLoad.Clear()
                [void]$searcher.PropertiesToLoad.Add("$memberRange")

                $rangeBottom+=1500

                try {
                    ## should cause and exception if the $memberRange is not valid
                    $result = $searcher.FindOne() 
                    $rangedProperty = $result.Properties.PropertyNames -like "member;range=*"
                    $members +=$result.Properties.item($rangedProperty)          
                   
                     # UPDATE - 2013-03-24 check for empty group
                      if ($members.count -eq 0) { $retrievedAllMembers=$true }
                }

                catch {

                    $retrievedAllMembers=$true   ## we received all members
                }

            }

        }

        $searcher.Dispose()
        return $members

    }
    return $false   
}