Showing posts with label PowerShell. Show all posts
Showing posts with label PowerShell. Show all posts

Monday, August 12, 2013

Using Active Setup to Update Anything in HKEY_CURRENT_USER

Following my last post, I had next to make sure that every user's entry in the registry was updated, and that change had to be scripted. This turned out to be a non-trivial task and took some research. First of all, the entry is located in HKEY_CURRENT_USER registy hive. Therefore, being logged in as Admin I cannot directly set an entry for Bob because Bob is not the current user at the moment. Then what can I do? The HKEY_CURRENT_USER is a kind of shortcut to HKEY_USERS. Under HKEY_USERS I can see the following structure

HKEY_USERS\.DEFAULT
HKEY_USERS\S-1-5-18
HKEY_USERS\S-1-5-19
HKEY_USERS\S-1-5-20
HKEY_USERS\S-1-5-21-0123456789-012345678-0123456789-1004
HKEY_USERS\S-1-5-21-0123456789-012345678-0123456789-1004_Classes

The first 4 entries correspond to built-in system accounts, and the rest are real user accounts on a PC. So, one way to make the change I need is to loop through all users and make the changes as requested. Someone even wrote a VB script which does exactly that. My case is a bit different, though. I only have a small handful of users, but the change I'm making in the registry key depends on the user. So, maybe I can map a username to the registry key.

If I run the following from the command line wmic useraccount get name,sid, I will see a table similar to the following

Name            SID
Administrator   S-1-5-21-1180699209-877415012-3182924384-500
Guest           S-1-5-21-1180699209-877415012-3182924384-501
Tim             S-1-5-21-1180699209-877415012-3182924384-1004

Great. Now I can script my change and run it. However - it does not work. It appears that user hives are usually only loaded for currently logged in users. That complicates things.

Fortunately, I came across the alternative solution - use Active Setup. It's original use is, likely, to check if a specific version of the software is installed to help installers to install, uninstall and repair software. It can, however, be used to write pretty much anything in the HKCU of the user who logs on. Here's how it works:

When the user logs on, the following registry key is checked: HKCU\Software\Microsoft\Active Setup\Installed Components\

If the HKCU key is not found in the registry then the contents of the string value StubPath is executed. This is essentially all that's important, so here is my example.

reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Active Setup\Installed Components\MountDrive" /v "Version" /d "1" /t REG_SZ /f

reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Active Setup\Installed Components\MountDrive" /v "StubPath" /d "reg add HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run /v "MountDrive" /d "C:\map.cmd" /t REG_SZ /f" /f

Or, translating the reg add commands into PowerShell script

$mapcmd = "C:\map.cmd"
$regKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\MountDrive"
New-Item -path $regKey | Out-Null
$regKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\MountDrive"
$regName = "Version"
$value = "1"
New-ItemProperty -path $regKey -name $regName -value $value | Out-Null
$regName = "StubPath"
$value ="reg add HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run /v MountDrive /d $mapcmd /f"
New-ItemProperty -path $regKey -name $regName -value $value | Out-Null

Here's what happens when the user logs on:

  • HKCU\Software\Microsoft\Active Setup\Installed Components\MountDrive is checked. There is nothing there.
  • string value in StubPath is executed. The value is "reg add" command and it creates a MountDrive string under Run key, with a value "C:\map.cmd". Therefore, this cmd script will run on user logon.
  • Also, a Version entry is created in HKCU\Software\Microsoft\Active Setup\Installed Components\MountDrive with a value of 1.
  • Next time the user logs on, step 1 find the Version entry, thefore no actions will be performed.

Seems a little complicated, but after running once and observing the changes as they are made in the registry, it becomes clear.

References:

Update a registry key for ALL users on a system
HKEY_USERS
Security Identifier
How To Find a User's Security Identifier (SID) in Windows
Adding Registry Settings
by . Also posted on my website

Saturday, June 29, 2013

Final Hurdles While Installing Windows Updates

There are three different PC models here, all configured in the same way, all have to have Windows Updates installed on them automatically. However, while two of the models were happily updated, the third decided it does not like some of the updates and presented me with the screen similar to the following:

Turns out these two error codes are quite known problems.

The first one is solved, among some other options, by downloading and installing a specific update: , which is over 300MB in size. So in my case I had to do a check for the PC model by reading it via PowerShell

$strModel = (Get-WmiObject Win32_ComputerSystem).Model.Trim()

and if the model was the "weird" one, install this update.

The second one is solved by repairing the .NET Framework 4 installation. Fortunately, this can be done either silently or with unattended option. All in all, the fix for my two problems was applied as the following addition to the script and, fortunately, no additional restarts were required and after running this bit I could proceed to install updates as per my previous post.

if($strModel -Like "*WeirdModel*")
{
 Write-Host "Verifying .NET Framework 4 ..."
 Start-Process "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\SetupCache\Client\setup.exe" "/repair /x86 /x64 /ia64 /parameterfolder Client /passive /norestart" -Wait
 Write-Host "Done."
 Write-Host "Installing System Update Readiness Tool ..."
 $readinessTool = Join-Path $win7Folder "Windows6.1-KB947821-v27-x64.msu"
 $toolCommand = $readinessTool + " /quiet /norestart"
 Write-Host $toolCommand
 Start-Process "wusa.exe" $toolCommand -Wait
 Write-Host "Done."
}

References:

Error Code 0x80073712 occurs in Windows Update or Microsoft Update
System Update Readiness Tool for Windows 7 (KB947821) [May 2013]
Error codes “0x80070643” or “0x643” occur when you install the .NET Framework updates
Silent install, repair and uninstall command lines for each version of the .NET Framework
by . Also posted on my website

Tuesday, June 25, 2013

Optimising Windows Updates Installation

It's a good thing that I wrote about Installing Windows Updates via Shell Script some time ago because today I needed to reuse that bit of a script and could not find it anywhere on my PC or our corporate network.

This time I'm reusing most of the functionality, but additionally do the following:

  • Make sure that the Windows Update service is started
  • Run a PowerShell script that passes a folder where the Windows update files are stored to the VbScript file
  • Execute VbScript to install all updates in a folder
  • Repeat. (I want to keep my "required" and "optional" updates separate

I was caught for a while trying to use PowerShell Set-Service and Start-Service commands and getting permission errors. I did not quite solve it, but found a simple workaround by utilising a command line:

@ECHO OFF

sc config wuauserv start= auto
net start wuauserv

Next, the PowerShell script is used to pass parameters to VbScript:

cscript .\Common\InstallUpdates.vbs $updatesFolder

Finally, the VbScript is almost the same as in the previous version, but note how the argument passed by PowerShell is parsed. The argument is the name of the folder where I placed the updates downloaded from Microsoft Update Catalog

Set args = WScript.Arguments
sfolder = args.Item(0)

Dim objfso, objShell
Dim iSuccess, iFail
Dim files, folderidx, Iretval, return
Dim fullFileName

Set objfso = CreateObject("Scripting.FileSystemObject")
Set folder = objfso.GetFolder(sfolder)
Set objShell = CreateObject("Wscript.Shell")

With (objfso)
 If .FileExists("C:\log.txt") Then
  Set logFile = objfso.OpenTextFile("C:\log.txt", 8, TRUE)
 Else
  Set logFile = objfso.CreateTextFile("C:\log.txt", TRUE)
 End If
End With

Set files = folder.Files
iSuccess = 0
iFail = 0

For each folderIdx In files

fullFileName = sfolder & "\" & folderidx.name

 If Ucase(Right(folderIdx.name,3)) = "MSU" then
  logFile.WriteLine("Installing " & folderidx.name & "...")
  iretval=objShell.Run ("wusa.exe " & fullFileName & " /quiet /norestart", 1, True)
  If (iRetVal = 0) or (iRetVal = 3010) then
   logFile.WriteLine("Success.")
   iSuccess = iSuccess + 1
  Else
   logFile.WriteLine("Failed.")
   iFail = iFail + 1
  End If
 ElseIf Ucase(Right(folderIdx.name,3)) = "EXE" Then
  logFile.WriteLine("Installing " & folderidx.name & "...")
  iretval = objShell.Run(fullFileName & " /q /norestart", 1, True)
  If (iRetVal = 0) or (iRetVal = 3010) then
   logFile.WriteLine("Success.")
   iSuccess = iSuccess + 1
  Else
   logFile.WriteLine("Failed.")
   iFail = iFail + 1
  End If
 End If
Next
 
wscript.echo iSuccess & " update(s) installed successfully and " & iFail & " update(s) failed. See C:\log.txt for details."

Disable the Windows Update service again if necessary

net stop wuauserv
sc config wuauserv start= disabled

References:

Managing Windows Services from the command line
Working with Command-Line Arguments
How do you pass a variable to VBS script in a powershell command?
Enable/Disable a Service via PowerShell
PowerShell queryService – Wait for a Dependency Starting Service
by . Also posted on my website

Tuesday, May 14, 2013

Customising Windows Installation

In some cases - for example, where the only purpose of the PC is to run a specific software package - certain Windows features are customised with the provider's brand. Such features may include logon screen background, individual desktop backgrounds for each user and user account logon pictures (tile images). Therefore it may be useful to know where those are stored and how to update them. The following description is for Windows 7 and Windows Server 2008.

1. Windows logon screen background.

This is the easiest one. It should be copied to the following location: C:\Windows\system32\oobe\info\backgrounds\backgroundDefault.jpg. A registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI\Background\OEMBackground should exist with a dword value of 00000001.

2. Desktop backgrounds (wallpaper) for each user

Check the following registry key: HKEY_CURRENT_USER\Control Panel\Desktop\Wallpaper. What I found was the following: C:\Users\Account_Name\AppData\Roaming\Microsoft\Windows\Themes\TranscodedWallpaper.jpg

Therefore, for each Account_Name the image has to be replaced with the desired one.

3. User Account logon pictures

This turned out to be trickier. There is a description on MSDN on how to do it manually [1]. Automating the task is not so obvious. Turns out, there is a function in the shell32.dll that sets a user login picture. It can be called easily from C# code by P/Invoke. In fact, the following is the full code of a console application that will update the user login picture.

using System;
using System.Runtime.InteropServices;

namespace UserPictureUpdater
{
    class Program
    {
        [DllImport("shell32.dll", EntryPoint = "#262", CharSet = CharSet.Unicode, PreserveSig = false)]
        public static extern void SetUserTile(string username, int notneeded, string picturefilename);
        [STAThread]
        static void Main(string[] args)
        {
            if (args.Length == 2)
            {
                SetUserTile(args[0], 0, args[1]);
            }
        }
    }
}

The application can run from command line

UserPictureUpdater Administrator adminnew.png

Or, in my case, I'm running it from PowerShell script the following way

Start-Process $command $params

Where $command is the full path, i.e. "C:\Folder\UserPictureUpdater.exe", and $params is the command line parameters, i.e. "Administrator adminnew.png".

Also, turns out someone figured out the way to do the whole thing in PowerShell [2]. But I was done with my approach by the time I found out.

References

About User Profiles
Setting the user tile image in Windows 7 and Server 2008 R2
by . Also posted on my website