PowerShell IE9 ComObject has all null properties after navigating to webpage - powershell

I have a PowerShell script that navigates to a (presumably) classic ASP page on our intranet to stop a Windows Service running on our server as part of the deployment process for that service (and restarts it after deploying the new files). It ran fine until we recently upgraded to IE9. Here's the script.
# Open service page in IE
$ie = new-object -comobject InternetExplorer.Application
$ie.visible = $true
$ie.navigate($serviceUrl)
while($ie.busy) { start-sleep 1 }
# Stop service
$ie.Document.getElementById("dropDownActionList").value = "Stop"
$ie.Document.getElementById("buttonTakeAction").click()
while($ie.busy) { start-sleep 1 }
Now when I run the script, it successfully launches IE, but throws the following error:
You cannot call a method on a null-valued expression.
At C:\Projects\ABC\Scripts\Deploy.ps1:85 char:28
+ $ie.Document.getElementById <<<< ("dropDownActionList").value = "Stop"
+ CategoryInfo : InvalidOperation: (getElementById:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
When I investigate in PowerShell, I find that if I create the IE ComObject, it at first has valid properties, but as soon as I navigate to the the service control page, all the properties are null (almost as if the ComObject gone away?). For example, before the HWND property had a valid value, but now it's null ($ie.hwnd -eq $null returns true). No error is displayed in PowerShell when I navigate to the page.
I looked at some similar questions, but the first one doesn't match my circumstance (the Document property is null in my case) and as for the latter one, IE9 defaults to compatibility mode for intranet sites. I saved the ASP page and ran it through the w3c validator and it threw some errors (although none related to the elements I'm trying to deal with). Unfortunately I can't fix those. Other sites don't seem to have this problem. Any suspicions on what the problem may be and recommendations on work-arounds?

I just worked through this.. sort of. I was seeing the same behavior until I turned off protected mode in IE. This seems to have something to do with submitting from one security zone to the next. So.. assuming that your original page is in the internet zone, with protected mode on, you submit to a page in a trusted zone or intranet or whatever, it seems like the COM context is lost. Probably intentional. I'm going to try fixing the zones, and keeping protected mode on.
Hope this helps.
EDIT: This is also a non-issue if you run your powershell in elevated mode (run as admin)

In addition:
http://msdn.microsoft.com/en-us/library/bb625962.aspx
This problem is caused by integrity levels since Internet Explorer 8.
That is also the reason, why the application runs well as administrator.
Since IE-8 runs in "low integrity" mode, it is not possible to automate IE from within a script. This is because the script runs as an user which belongs to "medium integrity" mode. The security design is such that it can send instructions from medium to low integrity, but can not receive data from low to medium integrity.
Update: Here is a working example how to do it without changing any settings. It gets back the lost com-Object.
function ConnectIExplorer() {
param($HWND)
$objShellApp = New-Object -ComObject Shell.Application
try {
$EA = $ErrorActionPreference; $ErrorActionPreference = 'Stop'
$objNewIE = $objShellApp.Windows() | ?{$_.HWND -eq $HWND}
$objNewIE.Visible = $true
} catch {
#it may happen, that the Shell.Application does not find the window in a timely-manner, therefore quick-sleep and try again
Write-Host "Waiting for page to be loaded ..."
Start-Sleep -Milliseconds 500
try {
$objNewIE = $objShellApp.Windows() | ?{$_.HWND -eq $HWND}
$objNewIE.Visible = $true
} catch {
Write-Host "Could not retreive the -com Object InternetExplorer. Aborting." -ForegroundColor Red
$objNewIE = $null
}
} finally {
$ErrorActionPreference = $EA
$objShellApp = $null
}
return $objNewIE
}
$HWND = ($objIE = New-Object -ComObject InternetExplorer.Application).HWND
$objIE.Navigate("https://www.google.com")
$objIE = ConnectIExplorer -HWND $HWND

Related

Using a PowerShell Script to change the default tray selected on a printer

I've been looking online at ways to automatically change the default tray on a printer. I'm in need of this because my job uses printers via an old IBM server, and these specific printers don't seem to respond to any instruction from the IBM server to change the tray used for the job. My only option to automatically change the preferences of a printer seems to be through a PowerShell script. I found a script on Stack that seems to achieve most of the functionality I want, however, I have very limited scripting knowledge and as a result, I'm unsure how to add the additional functionality required. First off, here's the code:
$Printer = "Example Printer Name"
$InputBin = "AutoSelect","AutoSheetFeeder","Cassette","Manual","Tractor" #choose one
Add-Type -AssemblyName System.Printing
$Permissions = [System.Printing.PrintSystemDesiredAccess]::AdministrateServer
$QueuePerms = [System.Printing.PrintSystemDesiredAccess]::AdministratePrinter
$PrintServer = new-object System.Printing.LocalPrintServer -ArgumentList $Permissions
$NewQueue = New-Object System.Printing.PrintQueue -ArgumentList $PrintServer,$Printer,1,$QueuePerms
$InputBinCaps = $NewQueue.GetPrintCapabilities().InputBinCapability
if ($null -ne $InputBinCaps) {
if ($InputBinCaps.Contains([System.Printing.InputBin]::$InputBin)) {
$NewQueue.DefaultPrintTicket.InputBin = [System.Printing.InputBin]::$InputBin
$NewQueue.UserPrintTicket.InputBin = [System.Printing.InputBin]::$InputBin
} else {
Write-Error "$InputBin unavailable on $Printer"
}
}
$NewQueue.commit()
$NewQueue.dispose()
$PrintServer.commit()
$PrintServer.dispose()
What I need to add is a way to detect what tray is currently set to default and use that data to direct an if statement to change it to the opposite (I only have two trays on these printers, Cassette 1 and Bypass Tray). Additionally, I need the script to immediately close its window upon execution of the task. The reason being is that I plan to put this as a shortcut on their Windows taskbar and have them click the shortcut every time they need to switch printer trays. I understand this setup is jank, but it is the only way to get the correct type of paper printed, save opening up the default tray, and laying one yellow sheet on top.
Any help would be greatly appreciated.
Thanks

Disabling Installshield's Next button with powershell

I have an Installshield project that uses powershell custom actions.
In one of my dialogs, I'm asking from the user to enter username and password, then I validate the credentials (with powershell) and I want to enable the Next button only if the credentials were correct.
Can this be achieved with powershell action item? The reason I'm using powershell is that I don't know InstallScript at all.
Here is my powershell script so far:
Function Test-UserCredential {
Param($username, $password)
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ct = [System.DirectoryServices.AccountManagement.ContextType]::Machine, $env:computername
$opt = [System.DirectoryServices.AccountManagement.ContextOptions]::SimpleBind
$pc = New-Object System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ct
$Result = $pc.ValidateCredentials($username, $password).ToString()
$Result
}
$comp_username = Get-Property -Name COMPUTER_USERNAME
$comp_password = Get-Property -Name COMPUTER_PASSWORD
$result = Test-UserCredential -username $comp_username -password $comp_password
if ($result)
{
#Enable "Next" button
}
else
{
#Disable "Next" button
}
Thanks.
There are three things you will have to do.
Choose a property that tracks whether the Next button should be enabled, and set that from your PowerShell. Typically you will set it to "1" or "" (empty string) for ease in the next step.
Create Control Conditions in the dialog editor that Enable and Disable the Next button referencing the property as your condition.
Separately trigger the UI to update after the powershell action completes. Unfortunately the UI does not evaluate control conditions after all property changes; it only does so after it changes a property it thinks is related. So the easiest way to do this is to add a Set Property control event that sets the property.
Note that for clarity of step 3's relevance, it can be useful to split this into two separate properties; set one in the powershell, reflect that into another in the Set Property control event, and have control conditions that read the latter.

Connect to TFS via Powershell with different user credentials

I've written a Powershell script which connects to our TFS server, creates a workspace, downloads the latest source and performs a nightly build and release.
The issue I have is that it always connects with my own credentials, and from what I've read this is because I'm logged onto the machine as me. I've had a new domain user account created and we've given this admin permissions within TFS, however I'm having trouble making the script use these credentials.
Here's the part of the script which deals with the initial connection and workspace creation as it currently stands:
$subfolder = [System.Guid]::NewGuid().ToString()
$tfsServer = "http://tfsserver:8080/tfs/xyz"
$tfsCollection = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($tfsServer)
$tfsVersionCtrlType = [Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer]
$tfsVersionCtrl = $tfsCollection.GetService([type] $tfsVersionCtrlType)
$tfsWorkspace = $tfsVersionCtrl.CreateWorkspace($subfolder, $tfsVersionCtrl.AuthenticatedUser)
For completeness, here's the rest of the "Get" logic:
$tfsWorkspace.Map($serverLocation, $localLocation)
$recursion = [Microsoft.TeamFoundation.VersionControl.Client.RecursionType]::Full
$versionSpec = [Microsoft.TeamFoundation.VersionControl.Client.VersionSpec]::Latest
$itemSpec = New-Object Microsoft.TeamFoundation.VersionControl.Client.ItemSpec($serverLocation, $recursion)
$fileRequest = New-Object Microsoft.TeamFoundation.VersionControl.Client.GetRequest($itemSpec, $versionSpec)
$getStatus = $tfsWorkspace.Get($fileRequest, [Microsoft.TeamFoundation.VersionControl.Client.GetOptions]::Overwrite)
The main problem I have when working with TFS via Powershell is the fact Microsoft keep changing their implementation, in some cases quite radically, and what we're left with is scraps of code posts and documentation littered around the internet (including here on SO) which refer to old addins and other now obsolete references which take you down a myriad of wrong paths.
Anyway, so I've had a play around with trying to create Windows credentials, PSCredentials, and the like (which don't seem to be accepted by anything), the old methods to supply an ICredential are now obsolete and I'm really not sure where to turn.
Basically, I just want to create a workspace, check out items, update files and check them back in - all as our new "tfsService" user account. Please help...
UPDATE:
Based on the answer from #Nick, I needed to make the following changes. Note the use of [System.Uri] which was required to get this working for me (not sure if that's a quirk of my setup as others didn't seem to require this). Also, I needed to put the constructor call for TfsTeamProjectCollection all on one line, as splitting it onto separate lines, as per Nick's example, wouldn't work for me either.
$cred = New-Object System.Net.NetworkCredential("username", "password", "domain")
$tfsCollection = New-Object Microsoft.TeamFoundation.Client.TfsTeamProjectCollection([System.Uri]$tfsServer, $cred)
#$tfsCollection = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($tfsServer)
$tfsVersionCtrlType = [Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer]
$tfsVersionCtrl = $tfsCollection.GetService([type] $tfsVersionCtrlType)
$tfsWorkspace = $tfsVersionCtrl.CreateWorkspace($subfolder, $tfsVersionCtrl.AuthenticatedUser)
I cannot test this effectively but this seems to be a common answer on SO for this issue.
$cred = New-Object NetworkCredential("myuser", "myPassword", "mydomain")
$tfsCollection = New-Object Microsoft.TeamFoundation.Client.TfsTeamProjectCollection
(
$tfsServer,
$cred
)

Can't download files with PowerShell IE COM Object [with login]

I have a script using PowerShells IE COM Object to login to a site and navigate to a page with PDFs (that I wish to download). I am using IE COM object to do all the navigation, and Invoke-WebRequest for the actual download. Issue is when the PDF files download they are size 2kb and can't be opened. I have a strong feeling this is a session issue and IE must be forgetting that I am logged in somehow. Any way to make IE remember I am logged in when so that I can download the files successfully?
Any help appreciated. Thanks.
Edit:
$ie = New-Object -COMObject InternetExplorer.Application
$ie.visible = $true
# Navigate to home page
$ie.navigate($URL)
while ($ie.Busy -eq $true){Start-Sleep -Seconds 3}
$Username = $ie.Document.getElementsByName("userName")
$Username.item(0).value = "username"
$Password = $ie.Document.getElementsByName("password")
$Password.item(0).value = "pass"
$AcceptTerms = $ie.Document.getElementById("useAddtnlField")
$AcceptTerms.checked = $true
$Btn = $ie.Document.getElementsByTagName("span") | ? {$_.title -match 'Login'}
$Btn.click()
while ($ie.Busy -eq $true){Start-Sleep -Seconds 2}
# ---- logged in ----
# -- Opens link in new window (due to JS) --
$ie.Navigate("https://chaseloanmanager.chase.com/Chaselock/ViewOnlineGuide.aspx")
while ($ie.Busy -eq $true){Start-Sleep -Seconds 2}
After that last navigate, I lose the $ie object and can no longer do anything with it. If I create a new $ie2 object, and continue the script using $ie2, the files are unable to be opened when finished. I was also unsuccessful getting access to the window using Shell.Application and Windows() method as to try and reference the original $ie window. Anyways, I think I need to complete the script using the original $ie object so that I am recognized as logged in throughout the process and can have a valid download at the end. Hopefully that makes sense and can help someone help me. Thanks

PowerShell Move User to different OU in different Domain with Credentials without Quest/ADAM/Cmdlets

I'm using
[System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($context, $idtype, $sam)
How do I move this use to a new OU?
I've tried:
...
$user_adspath = $user.Properties.adspath
$user_ou = [ADSI]"$user_adspath"
$user_ou.PSBase.MoveTo("LDAP://$target")
I recive a "General Access Denied" error. Due to the fact that I need rights. This works though.
...
$user.description += " MOVED"
$user.Enabled = $False
$user.Save()
Remember this is on a non-2008 server without Quest, ADAM, Cmdlets do not work. The only thing I have working is:
"Add-Type -AssemblyName System.DirectoryServices.AccountManagement"
I need something along the lines of this:
$user.MoveTo("LDAP://$target")
$user.Save()
You appear to be on the right track. Do you have write permissions in the target OU? It should be as simple as this:
[adsi]$OU="LDAP://OU=Disabled Accounts,OU=Employees,DC=mycompany,DC=local"
[adsi]$User="LDAP://CN=Art Deco,OU=Sales,OU=Employees,DC=mycompany,DC=local"
$user.psbase.Moveto($OU)
You don't need to load any assemblies or use anything else.
Figured it out:
[adsi]$dest = New-Object System.DirectoryServices.DirectoryEntry ("LDAP://ou=somwhere,dc=company,dc=local","domain\username","password")
$user_move = New-Object System.DirectoryServices.DirectoryEntry ("LDAP://cn=user,ou=somehow,dc=company,dc=local","domain\username","password")
$user_move.PSBase.MoveTo($dest)
Took awhile!