How to improve or optimize a particular Invoke-WebRequest in Powershell? - powershell

I have this Powershell command:
((Invoke-WebRequest https://www.intel.com/content/www/us/en/download/19344/intel-graphics-windows-10-windows-11-dch-drivers.html).AllElements | Where-Object -Property TagName -eq "META" | where -Property name -eq RecommendedDownloadUrl).content
I know that this can probably be done better, It's a specific question but I think I can learn a lot from your answers.
I want to just get the recommended URL from the META tag, to download the latest graphics driver from intel's website.
I ran one round of improvement, reducing Where-Object to just one command:
((Invoke-WebRequest https://www.intel.com/content/www/us/en/download/19344/intel-graphics-windows-10-windows-11-dch-drivers.html).AllElements | Where-Object {$_.TagName -eq "META" -and $_.name -eq "RecommendedDownloadUrl"}).content
Thanks!

You can at least return only the elements that match the name you want with:
(Invoke-WebRequest https://www.intel.com/content/www/us/en/download/19344/intel-graphics-windows-10-windows-11-dch-drivers.html).ParsedHtml.getElementsByName('RecommendedDownloadUrl')
If you are ok with not specifying that it is META you can just take that result and get the content of it:
$((Invoke-WebRequest https://www.intel.com/content/www/us/en/download/19344/intel-graphics-windows-10-windows-11-dch-drivers.html).ParsedHtml.getElementsByName('RecommendedDownloadUrl')).content
Assuming that they only have one element named RecommendedDownloadUrl that should work fine. It still parses the page, so it probably isn't much faster, but it works with the object's inherent methods rather than pumping tons of objects through a Where-Object filter.

This is probably the best way to do it - but if you want to optimize for speed, you could use the -UseBasicParsing switch to prevent PowerShell from spinning up a headless instance of Internet Explorer to parse the html (which is why it's slow):
$content = Invoke-WebRequest 'https://www.intel.com/content/www/us/en/download/19344/intel-graphics-windows-10-windows-11-dch-drivers.html' -UseBasicParsing |% Content
Now we have some raw html in a variable - now we just need to manually parse out the link in the relevant meta tag:
if($content -match '<meta\ name="RecommendedDownloadUrl"\ content="([^"]+)'){
# grab link from capture group
$Matches[1]
}
The reason I wouldn't consider this "better" than your current solution is that if intel makes changes to the HTML that wouldn't other affect the DOM, your script might break - if they suddenly switch the order of the attributes around it won't work anymore

Related

Unable to retrieve accessed folders from Event Logs using PowerShell

I am trying to control accesses to specific folder, so I have Audit Object Access policy enable and I've also enabled Auditing on the folder I want. Now I plan to see these accesses on a CSV file.
I have the following script that is supposed to achieve this
$OutputFileName = "EventsFrom-{0}.csv" -f (Get-Date -Format "MMddyyyy-HHmm")
Get-EventLog -LogName Security | Where-Object {$_.EventID -eq 4656} | Select-Object -Property TimeGenerated, MachineName, #{n='AccountName';e={$_.ReplacementStrings[1]}} | Export-CSV c:\scripts\$OutputFileName -NoTypeInformation
but the condition
Where-Object {$_.EventID -eq 4656}
causes the resulting CSV file to come out completely empty (even with no table headers). But when I change the Event ID (from 4656 to something like 4673) or remove the condition altoghether, I do get results on the resulting CSV.
Also, from the event viewer when I filter the results with the ID 4656, results do show up. Right now I genuinely don't know what to do. Thanks in advance for any help.
I appreciate if anyone could help me track down the cause for this. I don't really have much experience with PS scripting so a detailed expanation as to why this is happening (or the actual solution for my problem) would be very helpful.

Compare-Object API response with text file shows everything is different

I have a Web API that returns an HTML string which I want to compare with a html file on my local machine.
To do so, I have the following code
$Result = (Invoke-WebRequest `
-Uri "{uri}" `
-Headers #{"some-header", "some-value"}).Content
$TestContent = Get-Content -Path ($RepositoryLocation + "index.html") -Raw
$Equal = Compare-Object -ReferenceObject $TestContent -DifferenceObject $Result
When I now use Write-Hosts $Equal it displays me that the whole content is different
When I use Write-Host $Equal.SideIndicator it displays me => <= which also indicates that the complete file is different
Furthermore, using the command with -IncludeEqual -ExcludeDifferent displays empty result, so like I said, no lines are the same.
So what I tried next was to save the Content of $Result into a text file and compare them then, but still, it told me, that the whole file is different.
I then used diffchecker.com as well as JetBrains IDEs integrated comparison tool, to check for differences. Both tools told me that the content is identical. I'm losing my mind, why does PowerShell tell me they have complete different content?
Sadly, I cannot post the content of the API response as well as the content of the index.html
What I thought maybe could be the reason is
Encoding, however both are UTF8
Line endings, however no diff if I use CL, CL RF or RF on the file
Some hidden characters (tabs instead of spaces) but I activated to see that on JetBrains IDE and they still are identical.
How do I know what's causing this issue here?
Not sure if Compare-Object is the right choice here. As the name says, it's for objects. Why not use the equals-operator -eq or string.Equals?
$equal = $testContent -eq $result
# or
$equal = $testContent.Equals($result)
Compare-Object does not do line-by-line-comparison if both are just single strings. So your strings are not equal, as long as there's any tiny difference, as much as just an extra line-feed at the end, etc.
You could try several things:
# trim
$equal = $testContent.Trim() -eq $result.Trim()
# case-insensitive comparison
$equal = $testContent.Equals($result, 'OrdinalIgnoreCase')
# or both
$equal = $testContent.Trim().Equals($result.Trim(), 'OrdinalIgnoreCase')
Btw: If you do want a line-by-line-comparison, you have to split up the strings into lines first, e.g.:
Compare-Object ($testContent -split "`r`n") ($result -split "`r`n")

Get current version number of specified program

I started with this, on a recommendation from a friend
Get-WmiObject win32_product | ft name, version
But then I found this, which gives me pause.
A little research led me to this
wmic product where "Name='Revit 2018'" get name,version
Which works as far as the data gathered. And yes, I am looking for a different program in this example. in any case, once I had good info using WMIC I tried to get the data into a variable so I could get just the version number, but the data formatting is something I have never seen before. I was hoping for a simple solution, like
$object = wmic product where "Name='Revit 2018'" get name,version
$object.version
But only the result is an array with 6 items, and only one seems to be the actual data line, and that's a single line, not two properties. And, I really wonder if an old command line utility is the right answer here. If it really is the best way to do this, is there a trick to converting the raw data to something more, PowerShelly? And if it's not the best way to get this info, what is? Is that scary link real, or is Get-WmiObject win32_product actually safe? And if so, is there a way to filter on a specific name, to speed things up? And indeed, Get-WmiObject doesn't work as I was expecting, as
$object = Get-WmiObject win32_product | ft name, version
foreach ($item in $object) {
Write-Host "$($item.version)"
}
Doesn't work as expected at all.
EDIT: This seems to be working as expected, which is progress.
$version = (Get-WmiObject win32_product -filter:"Name = 'Revit 2018'" | Select-Object -property:*).version
Write-Host "$version!"
I guess the question is really, is this a safe and consistent approach, or is there a better one?
Why not use the registry?
Set-Location HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall
$app = Get-ChildItem | Where-Object { $_.GetValue("DisplayName") -match 'YourSoftware' }
$app.GetValue("DisplayVersion")
Or
Set-Location HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall
$apps = Get-ChildItem
foreach ($app in $apps) {
$app.GetValue("DisplayName","DisplayVersion")
}
Note: You'll also need to check the SysWow64 registry location as well
HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\
Note: Not all items will have a display version in which case you always have the option of looking in the installation directory for the executable itself, which should have a version on it.

Powershell 5.0 / ISE

I have an example code snippet that suggests using
(Get-Process | Where-Object {$_.WorkingSet64 -gt 20mb}).Count
to return the count of all processess using > 20Mb.
It works, but when typing, neither Intellisense or the "Tab" key shows this property, rather they show the properties of an individual process - which I find misleading.
I understand, that specifying an item property will give me the list of that property only, but is there a way to easily see, in general, what ALL the valid propeties are, including list aggregates etc?
Even assigning to a variable
$processes = Get-Process | Where-Object {$_.WorkingSet64 -gt 20mb}
does not show me "Count" as a valid property of $processes until AFTER the assignment has been actually run and the value assigned - when writing the script it still shows the properties for an individual item.
For me, Intellisense / Tab help that does not cover all the options kind of defeats the purpose ... (not having to remember hundreds objects/functions and their properties / parameters).
Is there any way to improve this situation? Some syntax trick have I missed?
The correct way to find out all of the properties of an object is to pipe the output to Get-Member:
Get-Process | Get-Member
Sometimes there are hidden properties and methods that can only be seen if you add the -force switch:
Get-Process | Get-Member -Force
The count property is an automatic property that is always usable on any collection object but that isn't explicitly listed as a property. Another example of an automatic property is length.
Using #() to force an array type is handy when that is what is wanted.
e.g. $processes = #(Get-Process | Where-Object {$_.WorkingSet64 -gt 20mb}). will show you "Count" and the other array properties.
Other than that, let's say the Intellisense has various limitations / shortcomings that I will just have to learn... sigh.

Copy block of text from webpage using PowerShell

I've extracted a whole web page as text and that text is assigned to a variable. Now I need to select a portion of that text and assign it to another variable. Let's say, the text I have is:
Note: Your feedback is very important to us, however, we do not
respond to individual submissions through this channel. If you require
support, please visit the Safety & Security Center. Follow: Change log
for version 1.211.2457.0 This page shows you what's changed in the
most recent definitions update for Microsoft antimalware and
antispyware software.
You can also see changes in the last 20 updates from the Change
definition version menu on the right.
The latest update is:
1.211.2457.0
Download the latest update.
 New definitions (?)
Antimalware (Antivirus + Antispyware)
I would like the following text to be assigned to a variable
1.211.2457.0
The code I have for now is
$URI = "http://www.example.com/mynewpage"
$HTML = Invoke-WebRequest -Uri $URI
$WebPageText = ($HTML.ParsedHtml.getElementsByTagName("div") | Where-Object{$_.className -eq "span bp0-col-1-1 bp1-col-1-1 bp2-col-1-1 bp3-col-1-1"}).innerText
I tried Select-String -SimpleMatch "The latest update is:*Download the latest update." -InputObject $WebPageText, but I'm pretty sure that's wrong.
I'm new to PowerShell scripting. So please pardon me if I'm missing something obvious.
Thank you in advance!
SimpleMatch would ignore any regex metacharaters. It would not allow any wildcards either. From TechNet:
Uses a simple match rather than a regular expression match. In a simple match, Select-String searches the input for the text in the Pattern parameter. It does not interpret the value of the Pattern parameter as a regular expression statement
What you could do is use regex to find a string where the line only contains digits and periods: "^[\d\.]+$".
$version = ($WebPageText | Select-String "^[\d\.]+$").Matches.Value
It is possible more that one could be returned so you might need to account for that.
If you wanted a more targeted (but no guaranteed unique result) you could just use the -match operator.
If(($WebPageText | out-string) -match "(?sm)The latest update is:\s+(.*?)\s+Download the latest update"){
$version = $Matches[1]
}