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

Thursday, June 26, 2008

Preventing items from being added to the MFU list

The old addage of "Prevention is better than cure" I have found to be true with relation to removing items from the Most Frequently Used (MFU) start menu list.

The following powershell script prevents the Windows Tour icon and Migration settings wizard from being added to the MFU list. Of course, this could be used to prevent any other number of items from being added to the MFU list.

<<<>>>

$TaskDesc = "Clear MFU List"
$AddRemoveApps_New = ";TourStart.exe;MigWiz.exe"
$AddRemoveApps = Get-ItemProperty HKLM:\Software\Microsoft\Windows\Currentversion\Explorer\FileAssociation
-name AddRemoveApps
Set-ItemProperty HKLM:\Software\Microsoft\Windows\Currentversion\Explorer\FileAssociation
-name AddRemoveApps -value $AddRemoveApps$AddRemoveApps_New


# Test to see if the Registry action succeeded
If ($?) {
Write-Host "Enabled $($TaskDesc)"

} Else {
Write-Host "Error:$($TaskDesc) $($Error[0])"

}

Frequently used programs not automatically added to the Start menu
http://support.microsoft.com/kb/q282066/

Tuesday, May 27, 2008

Powershell WMI one-liners

The PowerShell WMI capability is quite advanced and provides a vast scope for processing a returned recordset easily and effectively.

Here are just three types of Powershell WMI calls using different namespaces.

Return details about a service
get-wmiobject win32_service -filter "name='alerter'"

Return a users first name from Active Directory
(Get-WmiObject -namespace "root\directory\ldap" -class ds_user -filter "ds_Cn='[UserID]'").DS_givenname

Return details about a specified SMS package
Get-WmiObject -computer [SMSServer] -namespace root\sms\site_[SiteCode] -Query "SELECT * FROM SMS_Package WHERE PackageID='[PackageID]'"

Installing device drivers without the device being present

Having completed a number of SOE unattended installations, I have had reason to be able to install device drivers without the device being present. Attempts to install drivers without the device present using well known driver installation tools such as devcon.exe and dpinst.exe, failed to load the device drivers.

During investigation of the process I reviewed the device installation log file, %SystemRoot%\setupapi.log. In hindsight, the log file is quite aptly named in that functions within the setupapi.dll are called to install devices during windows setup and driver installation programs. This was shown by log entries from the devcon and dpinst programs.

Searches for setupapi.dll eventually showed that the SetupCopyOEMInf function is used to install the devices. Which in turn led to Pyron's SetupCopyOEMInf.exe utility. Whilst the utility is fantastic and performs device installation flawlessly, I was after something that could be manipulated if required.

Enter Powershell!

Utilising managed API calls, I was able to write a powershell script that calls the SetupCopyOEMInf function whilst maintaining the flexibility that scripting offers. Whilst by no means the right solution for everyone, this approach further demonstrates the capabilities of powershell.

In summary, the script installs the device drivers without the device being present.

During a standard user session, the device can be plugged in and the device drivers are loaded without prompting for Administrator credentials.



param(
[string] $DriverDir = $(throw "Please specify a directory of drivers to process")
)

$Scripts = Split-Path $Myinvocation.Mycommand.Path
# Copy a Driver to the OEM path (Install driver without the device being present)
#
#
# References
# http://monadblog.blogspot.com/2005/12/calling-win32-api-functions-through.html
# http://www.pinvoke.net/default.aspx/setupapi.SetupCopyOEMInf
#
# Set Error Handling
$ErrorActionPreference = "SilentlyContinue"
$Me = "SetupCopyOEMInf"
$provider = new-object Microsoft.VisualBasic.VBCodeProvider

$params = new-object System.CodeDom.Compiler.CompilerParameters
$params.GenerateInMemory = $True
$refs = "System.dll","Microsoft.VisualBasic.dll"
$params.ReferencedAssemblies.AddRange($refs)
# Find all the .inf files in the specified directory

$TaskDesc = "Locate INF Files"
$TempDir = Get-ChildItem $DriverDir -filter '*.inf' -recurse
If ($? -eq $False) {
Write-Host "Invalid directory $($DriverDir)"
Exit 1
}

$txtCode = @'
Class CopyOEMInf
Private Declare Function SetupCopyOEMInf Lib "setupapi.dll" Alias "SetupCopyOEMInfA" (ByVal SourceInfFileName As String, ByVal OEMSourceMediaLocation As String, ByVal OEMSourceMediaType As Long, ByVal CopyStyle As Long, ByVal DestinationInfFileName As String, ByVal DestinationInfFileNameSize As Long, ByRef RequiredSize As Long, ByVal DestinationInfFileNameComponent As String) As Long

Public Function Main(ByVal sInf As String, ByVal sDir As String) As Long
Const SP_COPY_NEWER As Integer = &H4
Dim lngResult As Long

Dim ret2 As String
Dim ret3 As String

' Install the driver'
lngResult = SetupCopyOEMInf(sInf, sDir, 1, SP_COPY_NEWER, ret2, 255, 255, ret3)
Return(lngResult)
End Function
end class

'@

$cr = $provider.CompileAssemblyFromSource($params, $txtCode)
if ($cr.Errors.Count) {
$codeLines = $txtCode.Split("`n");

foreach ($ce in $cr.Errors) {
Write-Host "Code compilation error, line $($ce.Line - 1)"
write-host "Error: $($codeLines[$($ce.Line - 1)])"
write-host $ce
}
Throw "INVALID DATA: Errors encountered while compiling code"
}

$mAssembly = $cr.CompiledAssembly

$i = $mAssembly.CreateInstance("CopyOEMInf")
# Process each inf file found


$TempDir ¦ foreach {
Write-Host "Installing Driver $($_.Get_FullName().ToString())"

$r = $i.Main($_.Get_FullName().ToString(), $_.Get_Directory().ToString())
}



References
Troubleshooting Device Installation with the SetupAPI Log File
http://www.microsoft.com/whdc/driver/install/setupapilog.mspx
Pyron setupcopyoeminf
http://www.msfn.org/board/Unlimited-number-of-drivers-keeping-th-t43795.html&p=303959#entry303959

http://waynes-world-it.blogspot.com/
Thanks goes to Wayne for introducing the concept and code snippets for compiling managed code from unmanaged scripts.

Monday, April 28, 2008

PowerShell appears to "hang" when executed from a console application

When executing Powershell from within PSExec or VBScript .exec method, powershell appears to hang.

In summary, Powershell doesn’t play nice with console applications.

From what I can gather it maintains a separate footprint for its console streams and therefore, any console based applications relying on STDOUT, STDERR, STDIN manipulation do not operate as expected.

To reproduce using PSExec.exe;

Test.ps1
Write-Output “HELLO”

Psexec –s c:\windows\system32\windowspowershell\v1.0\powershell.exe C:\Test.ps1

Hit CTRL-C and the script outputs “HELLO” as expected.


This suggests that the streams are only returned to the calling console when the script exits.

Thursday, April 17, 2008

Ever needed an Automated RunAs?

I went down this path for a Microsoft Deployment Toolkit (BDD) SOE build, as it was running in the wrong context for inserting details into a SQL database (computers and members of an Active Directory Group had access but the local administrator account didn't).

This powershell script accepts a number of arguments and then launches the process as the specified user;

Usage:
Start-RunAs.ps1 process Path [Arguments] [Domain] [User] [Password]

Examples;

Start-RunAs.ps1 "notepad.exe" "$($Env:SystemRoot)\System32"
Start-RunAs.ps1 "notepad.exe" "$($Env:SystemRoot)\System32" "" "domain"
"user"
Start-RunAs.ps1 "notepad.exe" "$($Env:SystemRoot)\System32" ""
"password" "user" "password"



The script could do with some tidying up for day to day use, but you get the idea...


Whilst I have some misgivings about the security of the "SecureString" it may be useful for some user's who wish to run their shell using Principle of Least Privilege...

Probably most notably though was the exercise and the way the PSH team decided to implement the SecureString concept.

It doesn't seem too secure, but as the PSH team offer, it wasn't meant to be, after all the owner of the process (interactive user) already knows the password they just typed in.


The following Powershell code snippet suggests that the password entry is secure. However, in the current session, the password can be found.

$PWD = Read-Host "Enter your password" -assecurestring[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServi
ces.Marshal]::SecureStringToBSTR($PWD))


Running the above commands results in;
Enter your password:: ****
System.Security.SecureString
Test


Still from the angle of security by obscurity, it would stop most prying eyes.


A simpler Runas I hear you ask?

$psi = New-Object System.Diagnostics.ProcessStartInfo "notepad.exe"
$psi.Verb = "runas"
[System.Diagnostics.Process]::Start($psi)

This launches the RunAs GUI, and then the process.

ConvertTo-SecureString
http://technet.microsoft.com/en-us/library/bb978707.aspx

http://www.leeholmes.com/blog/PowerShellCredentialsAndGetNetworkCredential.aspx

Windows PowerShell Securing the Shell
http://technet.microsoft.com/en-us/magazine/cc137808.aspx


param( [string] $FileName = $(throw "Please specify a filename to execute"),
[string] $FilePath = $(throw "Please specify a path to the filename"),
[string] $Arguments,
[string] $UserDomain,
[string] $UserName,
[string] $UserPassword
)

$Scripts = Split-Path $Myinvocation.Mycommand.Path
# Start a specified process as a specific user (RunAs)
#Set Error Handling
$ErrorActionPreference = "SilentlyContinue"
$Me = "Start-RunAs"
# Ensure the supplied filename/path exists

$TaskDesc = "Verify FileName exists"
get-item $FilePath\$FileName
If ($? -eq $False) {
Write-Host " $($TaskDesc) $($Error[0])"
Exit 1
}
# When a username but no password has been supplied, request credentials from user

If ($UserName -ne "" -and $UserPassword -eq "") {
$TaskDesc = "Request User Credentials for $($UserName)"
$UserCred = Get-Credential "$($UserDomain)\$($UserName)"
If ($? -eq $True) {
$UserDomain = $UserCred.UserName.Split("\")[0]
$UserName = $UserCred.UserName.Split("\")[1]
$UserPassword = $UserCred.GetNetworkCredential().Password
} Else {
Write-Host " $($TaskDesc): $($Error[0])"
Exit 2
}
$UserPassword = $UserCred.GetNetworkCredential().Password
} Else {
# present a blank credentials window for the user to populate
$TaskDesc = "Request User Credentials"
$UserCred = Get-Credential
If ($? -eq $True) {
$UserDomain = $UserCred.UserName.Split("\")[0]
$UserName = $UserCred.UserName.Split("\")[1]
$UserPassword = $UserCred.GetNetworkCredential().Password
} Else {
Write-Host " $($TaskDesc): $($Error[0])"
Exit 2
}
}

# A local domain can also be specified
$DOMAINUSER = $UserName
$DOMAINUSERPASSWORD = $UserPassword
$DOMAINUSERDOMAIN = $UserDomain

$SleepInterval = 2

# Number of seconds to wait for process to exit
$psi = New-Object System.Diagnostics.ProcessStartInfo "$($FileName)"
# Configure other process execution parameters
$psi.UseShellExecute = $False

$psi.UserName = $DOMAINUSER
$psi.Password = ConvertTo-SecureString "$($DOMAINUSERPASSWORD)" -AsPlainText -Force
$psi.Domain = $DOMAINUSERDOMAIN
$psi.WorkingDirectory = $FilePath
# $psi.WindowStyle = "Hidden"
$psi.Arguments = $Arguments

$TaskDesc = "Execute process;

"Write-Host "$($TaskDesc)"
Write-Host "$($psi.WorkingDirectory)\$($psi.FileName) $($psi.Arguments) as user $($psi.Domain)\$($psi.UserName)"
$ProcessStart = [System.Diagnostics.Process]::Start($psi)

If ($?) {
While ($ProcessStart.HasExited -ne $TRUE) {
Start-Sleep -s $SleepInterval
}
$ProcessStart.HasExited
} Else {
Write-Host "$($TaskDesc) failed, $($Error[0])"
}