I’m writing a script automating useless tasks at work.
Usually, everything is ok but sometimes a JavaScript is run in order to load specific pages and then all my documents are empty.
Exemple:
$Page = New-Object -ComObject «InternetExplorer.Application»
...
$frame = $Page.document.frames
...
# The script navigates well, set some values get others everything is fine and then:
$frame.item(3).IHTMLDocument3_getElementById(«oAff1»).children[0].(...).click()
# It does the click, the browser loads the new page and:
$frame.item(3).document
# Returns nothing, it seems to load in IE but not for Powershell who still see a white page.
Any Ideas?
Related
I'm trying to scrape this webpage: https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/ADV990001, specifically looking to grab the latest Article # from the table for Windows Server 2016 (or alternatively, the smaller table, grabbing the corresponding SSU Package number), which today, would be 4465659.
I'm working on a script to automate grabbing this latest Servicing Stack Update, and also the latest Cumulative Update for Windows Server 2016. I've got the Cumulative Update section working, but the same approach doesn't yield a result on the above link.
For reference, my Cumulative Update approach is below. It parses the page, performs a few adjustments, and ends up with the KBID that I can feed into a download script, which I know works. The problem with using this against the above link, is that the page content appears to be dynamically populated from elsewhere, so I can't seem to get any actual table content returned in PowerShell, from which I would be able to interrogate further.
Also, I need to -UseBasicParsing as this is executing on a Windows Server, potentially with IE out of the picture.
Thanks!
Matt
$buildVersion = "14393"
$kbID = (Invoke-WebRequest -Uri 'https://support.microsoft.com/en-us/help/4000825' -UseBasicParsing).RawContent -split "`n"
$kbID = ($kbID | Where-Object { $_ -like "*heading*$buildVersion*" } | Select-Object -First 1)
$kbID = ((($kbID -split "KB", 2)[1]) -split "\s", 2)[0]
You can't do it with simple http client, because that page runs some javascript and more over it first redirects you to eula page that you need to check/accept. You may google for some rest api returning same data or some other source with static html.
If not - you still can automate it, but you'd need to use actual browser. I guess the most popular way to do it is to use Selenium (it will use FireFox, Chrome or IE). There are probably some other headless browser.
Below is the solution with firefox (install it if not yet), but I believe you can do it with other browsers too. You will also need a C# driver (WebDriver.dll, it's inside Selenium.WebDriver.3.14.0.nupkg) and Mozilla GeckoDriver (geckodriver.exe). You can download it from here: https://www.seleniumhq.org/download/
Put all files in the same folder with you powershell script. The script below:
Add-Type -Path "C:\stack\selenium\WebDriver.dll"
$driver = [OpenQA.Selenium.Firefox.FirefoxDriver]::new()
$driver.manage().timeouts().ImplicitWait = [timespan]::FromSeconds(5)
$driver.Navigate().GoToUrl("https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/ADV990001")
$driver.FindElementByCssSelector(".ng-untouched").Click() # check eula box
$driver.FindElementByCssSelector(".btn-primary").Click() # click accept button
#select rows of the seconds table
$data = $driver.FindElementsByTagName("table")[1].FindElementsByTagName("tr").text
$driver.quit()
$data | Select-String "Windows Server 2016"
You can't do this without invoking IE or using some other UI automation.
If you have not hit the page before you get prompted to accept the first page.
Once you hit this URL:
https://support.microsoft.com/en-us/help/4000825
.. you'll find yourself here:
https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/ADV990001
Once there the main source of this page does not directly contain the table, if you inspect the table element, then you see the href's, and even those will take you off to the MS Catalog page as a search, that you'd then have to interact with (and if you've neve been to that page, it will force you to accept that one as well). So, this stuff only gets render using the a browser.
So, that means, just getting to the table (excluding all the accepts steps), you'd have something like the below using IE. I am not going to act on that first URL, since you are already dealing with that.
# Get all tables on a web page.
$Url1 = 'https://support.microsoft.com/en-us/help/4000825'
$Url2 = 'https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/ADV990001'
$ie = New-Object -com InternetExplorer.Application
while ($ie.Busy) {Sleep 1}
$ie.navigate($Url2)
while ($ie.Busy) {Sleep 1}
$KBTable = ForEach ($table in $ie.Document.getElementsByTagName('table')){ $table }
$KBPattern = 'https.*KB\d{7}'
[regex]::Matches(($KBTable | ConvertTo-Xml).Objects.Object.Property.'#text',$KBPattern).Value
# Results
https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4093430
https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4093430
https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4465659
… then of course do as you wish with those links.
I already know Powershell is not really meant for GUI automation, but just the same I have a pretty large Powershell script that needs to perform a single GUI operation, that being a left mouse click. I have searched for hours and have not found a satisfactory solution. The closest I have found is an add on called Wasp, but it was written in 2009 and appears to be incomplete anyway.
Is there another add in available to solve this problem?
I cannot condone using this as a first resort, but it should work. Generally when dealing with web pages you want to use URL parameters as much as possible since webpage elements are volatile.
With that said you should be able to create an IE comobject and then select the element that way. For example:
First create an IE object:
$ie = New-Object -ComObject "InternetExplorer.Application"
$ie.navigate("YourPageURLHere.com")
$ie.visible = $true
You can manipulate certain elements by ID (ex: name="username") or even type. These can be easily found be viewing the source code of a webpage. Here are a few examples:
$Username = $ie.document.getElementByID("Username")
$Username.value = $UsernameValue
$SearchButton = $ie.Document.getElementsByTagName("button")
$SearchButton.click()
Even if the webpage changes, since we have it all attached to the IE object you can then grab the output elements that generate after the submit button is clicked and feed them back into your script.
This is the first time I'm encountering GetLayoutObjectAttribute and I am having serious issues with it. My variable $web won't set. I think it's because PD_WebV isn't the right object name to refer to, but I don't know how to find the right object name. I can't find the objects name when I hit Edit Layout, so does anyone know how to find an layout objects name?
Loop
Pause/Resume Script [Duration (seconds): 1]
Set Variable[$Web; Value: GetLayoutObjectAttribute("PD_WebV";"content")]
If[$Web="done"]
#execute if statements
After Edit:
After some troubleshooting, I found out that PD_WebV is the right object name to refer and it's refered to correctly, so my new question is why doesn't the script go to the line If[$Web="done"] and how could I fix it? Ss my If statement not evaluating something it should be? Is my $web variable never set to done or is the issue something completely different? Would the problem possibly have to do with my WebDirect sharing settings? Any guidance would help. Thanks.
After, After Edit:
So now that my application is getting past Set Variable[$Web; Value: GetLayoutObjectAttribute("PD_WebV";"content")], the variable $web only equals <HTML></HTML>. Does anyone know a way, without using javascript, to test the inside of the html tags?
Also, I printed the bounds of the webViewer PD_WebV that I can't locate on the layout but am referring to in the script. The bounds that are printed are different each time I run the script. Is the usual or unusual? My source is also about:blank so it doesn't look like I'm sourcing from a URL
Is my $web variable never set to done or is the issue something
completely different?
If you're doing:
Set Variable[$Web; Value: GetLayoutObjectAttribute("PD_WebV";"content")]
then the only time
$Web="done"
will return true is when the web page loaded into your web viewer contains exactly the string "done" (in practical terms, that's never).
I have already suggested in a comment that you test for:
PatternCount ( $webpage ; "</html>" )
This is assuming you want the subsequent steps to execute only after the page has finished loading. The entire script would look something like this:
Loop
Pause/Resume Script [Duration (seconds): 1]
Set Variable[$Web; Value: GetLayoutObjectAttribute("PD_WebV";"content")]
Exit Loop If [ PatternCount ( $webpage ; "</html>" ) ]
End Loop
# execute statements
You might also want to add a counter to the loop and exit the script after n trials.
Ah, I reread your question.
To set the object name for your webviewer so that the GetLayoutObjectAttribute function works you need to set it in the Name field in the inspector when you have the webviewer selected.
e.g.:
After that your variable should populate.
Be aware
What it will populate with will be all of the html from the browser, i.e. not a boolean true/false liek your conditional suggests.
I'm not sure exactly what you're trying to accomplish, but to be able to determine a result from your web viewer you'll need to either parse the HTML to see if it's content contains what you're looking for or within the code you're setting the webviewer with, fire a javascript function that calls back to the FileMaker file using a FileMaker url.
I am trying to create a powershell script that pulls up a simple GUI form, displays "Loading..." in the title bar while it takes about 10-20 seconds to load the data, then finally change the title to "Please choose from the following options..." after loading.
I've been following this guide pretty much down to the letter. So far everything works, except for the fact that I get a "Parameter Count Mismatch" error when I execute this line:
$objListbox.BeginInvoke(
[Action[string]] { param($Message) $objListbox.Items.Add = $Message },
$MyMessage
)
Basically what I'm trying to do is load up about two thousand names into a multiselect listbox, then allow users to select the names they want to import elsewhere. So far everything works, I just have to hit continue on the Powershell ISE editor, but I'd like to get rid of this error all together.
BeginInvoke is rather older than generics in the .NET framework, and considerably older than the delegate Action<T>. Specifically you cannot pass an instance of Action<T> to BeginInvoke.
Try casting to System.Delegate instead.
I want to write a powershell script that will open an Internet Explorer instance as a com object so i can manipulate it. The problem is I need to run it with a parameter to make sure IE is run as a new session.
The parameter I need to pass is -noframemerging I need to pass this so each instance is a new session. Here is the code I've been using to get the instance started.
$ie = new-object -com "InternetExplorer.Application"
If someone could give me a hand figuring out how to be able to create multiple objects as a new session in IE that would be fantastic!
Here is my solution:
$IEProcess = [System.Diagnostics.Process]::Start("iexplore", "-noframemerging")
Sleep -Seconds 1
$ie = $(New-Object -ComObject "Shell.Application").Windows() | ? {$_.HWND -eq $IEProcess.MainWindowHandle}
I launch an instance with the System.Diagnostic.Process library, I was using Start-Process but getting some weird outcomes, this fixed that right up. At this point I have an instance of IE open with the noframemerging switch and a variable pointing the process.
I threw a sleep in there to give the com object a chance to construct before trying to reference it.
Then I grab all the windows com objects and select the one that has a handle (HWND) that is the same as the one from the process I just started and assign it to my $ie variable.
The end result is I have a variable that references a com object of Internet Explorer that is independent of all other sessions. The reason I needed this in particular is I'm opening many instances of IE and logging into a website with different user accounts, if they share a session the login page is skipped and they will already be logged in as the first user that logged in.
Thanks for all the help guys!