I'm getting the list of installed Microsoft Store apps with this command:
Get-AppxPackage -AllUsers
And then I try to open an app:
powershell -Command "Start-Process 'C:\Program Files\WindowsApps\Microsoft.Windows.Photos_2021.21070.22007.0_x64__8wekyb3d8bbwe\Microsoft.Photos.exe' -Verb runAs"
I get an access error:
This command cannot be run due to the error: Access is denied.
# Use the URI scheme of the Microsoft.Photos application.
# Note: Unfortunately, -Wait does *not* work in this case.
Start-Process ms-photos:
# Wait for the process to exit (from what I can tell there's only ever 1
# Microsoft.Photos process).
# The relevant process name was obtained with: Get-Process *Photos*
(Get-Process Microsoft.Photos).WaitForExit()
Note: That Start-Process -Wait (and -PassThru) cannot be used with at least some Microsoft Store applications is unfortunate; the problem has been reported in GitHub issue #10996.
Using a URI protocol scheme such as ms-photos: is the simplest approach, although discovering a given Microsoft Store's application's protocol(s) is non-trivial - see this answer, which provides a helper function, Get-AppXUriProtocol, which builds on the standard
Get-AppXPackage cmdlet; e.g.:
# Note:
# * Requires custom function Get-AppXUriProtocol from the linked answer.
# * Must be run in *Windows PowerShell*, because the AppX module
# isn't supported in PowerShell (Core), as of v7.1.
PS> Get-AppXUriProtocol *Photos* | Format-List
PackageFullName : Microsoft.Windows.Photos_2021.21070.22007.0_x64__8wekyb3d8bbwe
Protocols : {ms-wcrv, ms-wpdrmv, ms-photos, microsoft.windows.photos.crop...}
As you can see, the Microsoft Photos application has several protocol schemes associated with it, but the obvious candidate for simply launching the application is ms-photos:, which indeed works.
Launching Microsoft Store applications that do not have a URI protocol scheme defined:
If a given application doesn't define a URI protocol scheme, you must - somewhat obscurely - launch it via its AppId (application ID), and the general shell: URI protocol scheme and the virtual AppsFolder shell folder; e.g., to launch Calculator:
Start-Process shell:AppsFolder\Microsoft.WindowsCalculator_8wekyb3d8bbwe!App
Finding an application's AppID:
In Windows 10 and above, you can now use the Get-StartApps cmdlet to list all installed AppX applications or search by (part of) their display name, which reports their AppIDs. Thus, if you know an application's full display name, e.g., Photos, you can launch it as follows:
Start-Process "shell:AppsFolder\$((Get-StartApps Photos | Select-Object -First 1).AppId)"
Note: The reason for Select-Object -First 1 is that even specifying the exact display name can result in multiple results, such as for Microsoft Edge.
If you're unsure of the full display name, you can use a substring to find matching applications; e.g.. Get-StartApps edge
In older versions, you must determine the AppID manually, which is quite cumbersome, unfortunately: read on.
The AppID is composed of the family package name (e.g. Microsoft.MicrosoftEdge_8wekyb3d8bbwe) followed by ! and a package-internal identifier, which is typically - but not always !App; two notable exceptions:
Spotify requires !Spotify (as you've discovered yourself).
Microsoft Edge uses !MicrosoftEdge (note, however, that Edge does have a URI protocol for lauching, microsoft-edge: and that there's also a simpler AppID, MSEdge, though it doesn't support passing arguments (see below)).
As you have discovered yourself, the suffix is the package-internal application ID, which is defined in an application's manifest file, appxmanifest.xml, located in the app-specific subfolder underneath $env:Programfiles\WindowsApps; note that a manifest can contain multiple application IDs, as is indeed the case for Microsoft Photos:
# Run in *Windows PowerShell*.
# For Microsoft Photos, the following application IDs are reported:
# 'App', 'SecondaryEntry'
$appManifestPath = (Get-AppxPackage *Photos*)[-1].InstallLocation + '\appxmanifest.xml'
(
Select-Xml '//ns:Application' $appManifestPath `
-Namespace #{ ns='http://schemas.microsoft.com/appx/manifest/foundation/windows10' }
).Node.Id
It's fair to assume for launching the application interactively that the first entry must be used, though I'm not clear on whether there are official rules.
To demonstrate the technique using Microsoft Photos:
# Get the package family name (the assumption here is that only *1* package matches).
# Note: While you must run the Get-AppXPackage from *Windows PowerShell*
# you can use the resulting package name to launch the application
# from PowerShell (Core) too.
$packageFamilyName = (Get-AppXPackage *Photos*).PackageFamilyName
Start-Process "shell:AppsFolder\$packageFamilyName!App"
Note that since the executable is then launched indirectly, the actual target process (Microsoft.Photos in this case) isn't guaranteed to exist yet when Start-Process returns, so more work is needed to first wait for it to come into existence, and then wait for it to exit.
In the simplest - but not fully robust - case, insert a Start-Sleep command, to sleep as long as you would expect creation of the target process to take at most (the actual timing varies with system load):
Start-Process "shell:AppsFolder\$packageFamilyName!App"
Start-Sleep -Seconds 5 # Wait for the Microsoft.Photos process to be created.
(Get-Process Microsoft.Photos).WaitForExit()
A fully robust approach would require more work.
Passing arguments to Microsoft Store applications:
With the general "shell:AppsFolder\$appId" approach, you can seemingly pass argument as usual, via Start-Process' -ArgumentList (-Args) parameter; e.g., with Microsoft Edge (run from Windows PowerShell - if you have an older Edge version, replace !App with `!MicrosoftEdge:
# Starts Microsoft Edge and opens the specified URLs.
# Note: Curiously, this does NOT work with the simpler "MSEdge" AppID.
Start-Process `
shell:AppsFolder\Microsoft.MicrosoftEdge_8wekyb3d8bbwe!MicrosoftEdge `
-Args 'http://example.org https://wikipedia.org'
With the app-specific URI-scheme approach, argument(s) must be passed as part of the URI (-ArgumentList is ignored):
Caveat: It is unclear to me how you can pass multiple arguments and, generally, whether there is a standardized method across applications to embed arguments in the URI.
For instance, Microsoft Edge seems to accept only one argument: the URL of a site to open. Anything after that one URL is seemingly interpreted as a part of that one URL:
# Starts Microsoft Edge an opens the specified URL.
Start-Process 'microsoft-edge:https://en.wikipedia.org?search=wikipedia'
Related
I found this script to download Javax64 and it really works, but I had some problems.
The first is that the command I would put would be inside an XML file that a powershell script calls it, so putting it directly like this, it gave some errors because where it shows "<a" the XML understood that this was part of it and not of something that only PowerShell would make use of.
The second is "New-Object -ComObject "InternetExplorer.Application" where this is not working on my Windows Server and it is recommended not to use it for ie it is being discontinued soon. It still works on Windows 10 normally but on Windows Server it gets stuck in a loop and won't get out.
How would I convert this script to an Invoke-WebRequest, is this possible? Because then I would just need to put the complete string of the Invoke-WebResquest in my XML file and PowerShell would read it normally, I think.
$ie = New-Object -ComObject "InternetExplorer.Application"
# Navigate to the requested page
$ie.Navigate2("https://www.java.com/en/download/manual.jsp")
$anchor = $null
while($anchor -eq $null -or $anchor -eq "")
{
#wait 1 second for the page to load
start-sleep -m 1000
#get the html of the page
$html = $ie.document.body.innerHTML
#apply your regex to identify the anchar with the download link
$anchor = [regex]::Match($html, '(?:<a title="Download Java software for Windows \(64-bit\)" href=")(.*)(?:">)').Groups[1] .Value
}
#regex doesn't return the link correctly, that's why I made the substring to get the link
$url_download = $anchor.Substring(0,$anchor.IndexOf(""""))
$url_download
Edit: There is the same situation but to download Edge?
Note: Neither Invoke-WebRequest or the built-in .NET clients for obtaining files over HTTP seem to support rendering the full DOM, and so JavaScript cannot be executed. JavaScript is required to access those downloads links and use the site in general. You have two choices:
Use static links as I have outlined in my original answer below; or
Automate Edge using WebDriver, which is how Microsoft recommends you automate MS Edge. There is no COM functionality for controlling the Edge browser.
Unfortunately, I cannot help with the latter as I have no experience using WebDriver.
Looking at the oraclejdk Chocolatey package installation script, the URL is
https://download.oracle.com/java/17/archive/jdk-17.0.2_windows-x64_bin.msi. Since you're already familiar with Chocolatey from another environment, I would figure the version you need, see if there is a Chocolatey package for it, and get the direct URL from that package version's installation script.
You could also attempt to templatize the URL like so:
https://download.oracle.com/java/MAJOR_VERSION/archive/jdk-MAJOR_VERSION.MINOR_VERSION.PATCH_windows-x64_bin.msi
where MAJOR_VERSION, MINOR_VERSION, and PATCH are pieces of the Java version. However, I have not tested that all Java MSI URLs follow this pattern.
Regardless, once you have the URL, it's as simple as:
# Work around performance issue with iwr and the progress bar
$ProgressPreference = 'SilentlyContinue'
$MSI_URL = 'https://download.oracle.com/java/17/archive/jdk-17.0.2_windows-x64_bin.msi'
Invoke-WebRequest -UseBasicParsing $MSI_URL -Outfile 'jdk-17.0.2_windows-x64_bin.msi'
I've just started using PowerShell and I have a task where I need to be able to have the file path displayed on screen when I enter the file name.
Is there a script that allows me to do the below ? :
Ex 1: I enter "test.txt" and I get "C:\Program Files...."
Ex 2: I enter a file name "My Documents" and I also get its path.
I have searched online on how to do this but I didn't quite find what I was looking for and all the queries/answers were too complicated for me to understand.
Can anyone help me out, please?
Thanks in advance!
Here is a starter sample for you.
This example search only within the confine of the paths present is the Path system environment variable. It also only looks for files and do not recurse through these path.
So anything you could access directly from the command line should be available to you through it.
Now, if you want to search the whole drive, you could replace the $DefaultPaths assignment with Get-ChildItem -Path 'C:' -Recurse but doing that each time won't be super efficient.
You could do it and it will work... but it will be slow.
To search on the whole drive or whole filesystem, there are alternative methods that might work better. Some examples of what might entice:
Using a database which you have to buld & maintain to index all the files so that when you search, results are instantaneous and / or very fast
Parsing the MFT table (if using Windows / NTFS filesystem only) instead of using Get-ChildItem (This is not somehting natively doable through a simple cmdlet though) .
Relying on a third party software and interface with (For example, Void Tools Everything search engine already parse MFT and build its own database, allowing users to search instantly through a Windows NTFS filesystem. It also have its own SDK you can plug in through Powershell and retrieve what you seek instantly. The caveats is that you need the software installed first for that solution to work.)
Example: Searching through all paths defined in the Path variable
# What you are looking for. Accept wildcards characters (*)
$Filter = 'notepad.exe'
# Get the System Environment Path variable in an array
$DefaultPaths = $env:Path -split ';'
$Paths =
Foreach ($P in $DefaultPaths) {
# Search for files matching the specified filter. Ignore errors (often if the path do not exist but is sin the Path)
$MatchingFiles = Get-ChildItem -Path $P -Filter $Filter -File -ErrorAction SilentlyContinue
if ($MatchingFiles.count -gt 0) {
$MatchingFiles.Directory.FullName
}
}
$Paths | out-string | Write-Host -ForegroundColor Cyan
Output for Notepad.exe search using this method.
C:\Windows\system32
C:\Windows
I followed this procedure in order to permanently add a path to SumatraPDF using powershell. The last few commands from the link are meant to check that the path has indeed been added.
When I access the path using the following command,
(get-itemproperty -path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path.split(';')
the result includes the path to SumatraPDF
C:\Windows\system32
C:\Windows
C:\Windows\System32\Wbem
C:\Windows\System32\WindowsPowerShell\v1.0\
C:\Windows\System32\OpenSSH\
C:\ProgramData\chocolatey\bin
C:\texlive\2021\bin\win32
C:\Users\921479\AppData\Local\SumatraPDF
However when I access it using the following command,
($env:path).split(';')
the result does not contain the path to SumatraPDF:
C:\Windows\system32
C:\Windows
C:\Windows\System32\Wbem
C:\Windows\System32\WindowsPowerShell\v1.0\
C:\Windows\System32\OpenSSH\
C:\ProgramData\chocolatey\bin
C:\texlive\2021\bin\win32
C:\Users\921479\AppData\Local\Microsoft\WindowsApps
Finally, actually passing sumatrapdf does not works, which indicates to me that the real path is the one accessed using the get-itemproperty command.
Why does the path set in the registry not correspond to the one set in $env:path? Is there a mistake in the procedure shown in the link I followed? How can I correct it?
I should mention I have already tried restarting the shell but it doesn't help.
Note:
See the middle section for helper function Add-Path
See the bottom section for why use of setx.exe should be avoided for updating the Path environment variable.
The procedure in the linked blog post is effective in principle, but is missing a crucial piece of information / additional step:
If you modify environment variables directly via the registry - which, unfortunately, is the right way to do it for REG_EXPAND_SZ-based environment variables such as Path - you need to broadcast a WM_SETTINGCHANGE message so that the Windows (GUI) shell (and its components, File Explorer, the taskbar, the desktop, the Start Menu, all provided via explorer.exe processes) is notified of the environment change and reloads its environment variables from the registry. Applications launched afterwards then inherit the updated environment.
If this message is not sent, future PowerShell sessions (and other applications) won't see the modification until the next logon / reboot.
Unfortunately, there's no direct way to do this from PowerShell, but there are workarounds:
Brute-force workaround - simple, but visually disruptive and closes all open File Explorer windows:
# Kills all explorer.exe processes, which restarts the Windows shell
# components, forcing a reload of the environment from the registry.
Stop-Process -Name explorer
Workaround via .NET APIs:
# Create a random name assumed to be unique
$dummyName = [guid]::NewGuid().ToString()
# Set an environment variable by that name, which makes .NET
# send a WM_SETTINGCHANGE broadcast
[Environment]::SetEnvironmentVariable($dummyName, 'foo', 'User')
# Now that the dummy variable has served its purpose, remove it again.
# (This will trigger another broadcast, but its performance impact is negligible.)
[Environment]::SetEnvironmentVariable($dummyName, [NullString]::value, 'User')
Workaround by calling the Windows API via an ad hoc-compiled P/Invoke call to SendMessageTimeout() in C#, via Add-Type:
While this is a proper solution, it invariably incurs a noticeable performance penalty due to the ad hoc-compilation the first time it is run in a session.
For details, see this blog post.
The approach in the blog post has another problematic aspect:
It retrieves the expanded environment-variable value from the registry, because that is what Get-ItemProperty and Get-ItemPropertyValue invariably do. That is, if directories in the value are defined in terms of other environment variables (e.g., %SystemRoot% or %JAVADIR%), the returned value no longer contains these variables, but their current values. See the bottom section for why this can be problematic.
The helper function discussed in the next section addresses all issues, while also ensuring that the modification takes effect for the current session too.
The following Add-Path helper function:
Adds (appends) a given, single directory path to the persistent user-level Path environment variable by default; use -Scope Machine to target the machine-level definition, which requires elevation (run as admin).
If the directory is already present in the target variable, no action is taken.
The relevant registry value is updated, which preserves its REG_EXPAND_SZ data type, based on the existing unexpanded value - that is, references to other environment variables are preserved as such (e.g., %SystemRoot%), and may also be used in the new entry being added.
Triggers a WM_SETTINGCHANGE message broadcast to inform the Windows shell of the change.
Also updates the current session's $env:Path variable value.
Note: By definition (due to use of the registry), this function is Windows-only.
With the function below defined, your desired Path addition could be performed as follows, modifying the current user's persistent Path definition:
Add-Path C:\Users\921479\AppData\Local\SumatraPDF
If you really want to update the machine-level definition (in the HKEY_LOCAL_MACHINE registry hive, which doesn't make sense with a user-specific path), add -Scope Machine, but not that you must then run with elevation (as admin).
Add-Path source code:
function Add-Path {
param(
[Parameter(Mandatory, Position=0)]
[string] $LiteralPath,
[ValidateSet('User', 'CurrentUser', 'Machine', 'LocalMachine')]
[string] $Scope
)
Set-StrictMode -Version 1; $ErrorActionPreference = 'Stop'
$isMachineLevel = $Scope -in 'Machine', 'LocalMachine'
if ($isMachineLevel -and -not $($ErrorActionPreference = 'Continue'; net session 2>$null)) { throw "You must run AS ADMIN to update the machine-level Path environment variable." }
$regPath = 'registry::' + ('HKEY_CURRENT_USER\Environment', 'HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment')[$isMachineLevel]
# Note the use of the .GetValue() method to ensure that the *unexpanded* value is returned.
$currDirs = (Get-Item -LiteralPath $regPath).GetValue('Path', '', 'DoNotExpandEnvironmentNames') -split ';' -ne ''
if ($LiteralPath -in $currDirs) {
Write-Verbose "Already present in the persistent $(('user', 'machine')[$isMachineLevel])-level Path: $LiteralPath"
return
}
$newValue = ($currDirs + $LiteralPath) -join ';'
# Update the registry.
Set-ItemProperty -Type ExpandString -LiteralPath $regPath Path $newValue
# Broadcast WM_SETTINGCHANGE to get the Windows shell to reload the
# updated environment, via a dummy [Environment]::SetEnvironmentVariable() operation.
$dummyName = [guid]::NewGuid().ToString()
[Environment]::SetEnvironmentVariable($dummyName, 'foo', 'User')
[Environment]::SetEnvironmentVariable($dummyName, [NullString]::value, 'User')
# Finally, also update the current session's `$env:Path` definition.
# Note: For simplicity, we always append to the in-process *composite* value,
# even though for a -Scope Machine update this isn't strictly the same.
$env:Path = ($env:Path -replace ';$') + ';' + $LiteralPath
Write-Verbose "`"$LiteralPath`" successfully appended to the persistent $(('user', 'machine')[$isMachineLevel])-level Path and also the current-process value."
}
The limitations of setx.exe and why it shouldn't be used to update the Path environment variable:
setx.exe has fundamental limitations that make it problematic, particularly for updating environment variables that are based on REG_EXPAND_SZ-typed registry values, such as Path:
Values are limited to 1024 characters, with additional ones getting truncated, albeit with a warning (as of at least Windows 10).
The environment variable that is (re)created is invariably of type REG_SZ, whereas Path is originally of type REG_EXPAND_SZ and contains directory paths based on other environment variables, such as %SystemRoot% and %JAVADIR%.
If the replacement value contains only literal paths (no environment-variable references) that may have no immediate ill effects, but, for an instance, an entry that originally depended on %JAVADIR% will stop working if the value of %JAVADIR% is later changed.
Additionally, if you base the updated value on the current session's $env:Path value, you'll end up duplicating entries, because the process-level $env:Path value is a composite of the machine-level and current-user-level values.
This increases the risk of running into the 1024-character limit, especially if the technique is used repeatedly. It also bears the risk of duplicate values lingering after the original entry is removed from the original scope.
While you can avoid this particular problem by retrieving the scope-specific value either directly from the registry or - invariably in expanded form - via [Environment]::GetEnvironmentVariable('Path', 'User') or [Environment]::GetEnvironmentVariable('Path', 'Machine'), that still doesn't solve the REG_EXPAND_SZ problem discussed above.
Use setx to permanently update an environment variable. Don't hack the registry.
After you invoke setx, just update the Path environment manually in the current session. Powershell: Reload the path in PowerShell
How can I create a protocol handler for a powershell script and make the target powershell script receive command line arguments?
And what are the security concerns in doing so?
I thought I write up a decent guide on doing so since the information I found online was lacking some details.
https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85)
First off the security concerns
Any program, website, script etc. that is running on your computer can set off the protocol. There are no authorization checks.
You should NOT create a universal protocol handler. That would be a massive security issue concern. I mean that would enable a program, website, script etc. to run any powershell script or command on your computer.
Creating the protocol handler in Windows registry
The protocol must be registered in Windows Registry. It's a simple task.
I'm calling my powershell protocol handler for pwsh
Step 1: Open the Registry Editor and navigate to
Computer\HKEY_CLASSES_ROOT
For inspiration you can look at Computer\HKEY_CLASSES_ROOT\http to look at how that protocol handler is made.
Step 2: Create the following hierarchy:
Create the key pwsh: [Computer\HKEY_CLASSES_ROOT\pwsh]
Edit the default value of (Default) to URL:pwsh. Remember I call my protocol handler for pwsh, write whatever your is called.
Add a string value with the name URL Protocol and empty data.
It should look like this now:
Create a new key under pwsh, DefaultIcon: Computer\HKEY_CLASSES_ROOT\pwsh\DefaultIcon.
Set the (Default) data field to a filepath that leads to an icon or image. I used the powershell icon for Powershell 7 C:\Program Files (x86)\PowerShell\7-preview\assets\ps_black_32x32.ico.
Then create the keys shell -> open -> command like shown on the image above.
In the key command change the (Default) data value to where powershell is installed and then the powershell script to be run.
When testing I do this: "C:\Program Files\PowerShell\6\pwsh.exe" -noexit -executionpolicy bypass -Command {Write-Host %1}
Note I am using powershell core 6 and your path to powershell is probably different.
You can test to check if it works by opening the run program in Windows(Windows+R).
Expected behavior is the powershell window to open with the text pwsh:Hello Stackoverflow printed.
Step 3: Create a powershell script to handle incoming actions on the protocol.
The production ready data value for the command key: "C:\Program Files\PowerShell\6\pwsh.exe" -noexit -File C:\handleActions.ps1 %1
Param($Argument="") # If the protocol is ran you always at least get the protocol name as an argument. (if using the %1)
[String]
$Argument
function Handle-Actions { # The cmdlet 'Handle-Actions' uses an unapproved verb.
[cmdletBinding()]
param(
[Parameter(Mandatory=$false, Position=0)]
[String]
$Argument
)
$Argumuments = $Argument.Split([Char]0x003F) # Splits by `?`
#Argumnets is now in an array, do whatever you need to next.
$Argumuments | %{
Write-Host $_ # Writes each argument that was seperated by ? to a line
}
}
Handle-Actions -Argument $Argument
Given the run command pwsh:?firstArgument?SecondArgument the script will output:
pwsh:
firstArgument
SecondArgument
To complement your helpful guide with sample code that automates creation of a custom protocol handler:
The following:
Creates a custom URI protocol custom: (rather than pwsh:, given that PowerShell is simply used to implement the protocol) to which an open-ended number of arguments can be passed.
Does so for the current user only (HKEY_CURRENT_USER\Software\Classes) by default; however, it's easy to tweak the code to implement the custom protocol for all users instead (HKEY_LOCAL_MACHINE\Software\Classes), though you'll need to run the code with elevation (as administrator) then.
A handler *.ps1 script is automatically created:
At $env:USERPROFILE\customUriHandler.ps1 in the current-user scenario.
At $env:ALLUSERPROFILE\customUriHandler.ps1 in the all-users scenario.
The handler script simply echoes the arguments passed to it, and it is invoked in a PowerShell script window that is kept open after script execution (-NoExit); tweak the PowerShell command as needed.
The protocol expects its arguments as if it were a shell command, i.e., as a space-separated list of arguments, with argument-individual "..." quoting, if necessary.
The sample command at the end uses Start-Process to invoke the following URI, which you could also submit from the Run dialog (WinKey-R), which passes arguments one, two & three, four:
URI: custom:one "two & three" four
Invocation via Start-Process: Start-Process 'custom:one "two & three" four'
Caveat: If you submit this URI via a web browser's address bar (note: doesn't seem to work with Microsoft Edge), it is URI-escaped, and a single one%20%22two%20&%20three%22%20four argument is passed instead, which would require custom parsing; similarly, submitting from File Explorer's address bar passes one%20two%20&%20three%20four, though note that the " chars. are - curiously - lost in the process.
# Determine the scope:
# Set to $false to install machine-wide (for all users)
# Note: Doing so then requires running with ELEVATION.
$currentUserOnly = $true
if (-not $currentUserOnly) {
net session *>$null
if ($LASTEXITCODE) { Throw "You must run this script as administrator (elevated)." }
}
$ErrorActionPreference = 'Stop'
# The name of the new protocol scheme
$schemeName = 'custom'
$pwshPathEscaped = (Get-Process -Id $PID).Path -replace '\\', '\\'
$handlerScript = ($env:ALLUSERSPROFILE, $env:USERPROFILE)[$currentUserOnly] + "\${schemeName}UriHandler.ps1"
$handlerScriptEscaped = $handlerScript -replace '\\', '\\'
# Create the protocol handler script.
#'
# Remove the protocol scheme name from the 1st argument.
$argArray = $args.Clone()
$argArray[0] = $argArray[0] -replace '^[^:]+:'
# If the 1st argument is now empty, remove it.
if ('' -eq $argArray[0]) { $argArray = $argArray[1..($argArray.Count-1)] }
"Received $($argArray.Count) argument(s)."
$i = 0
foreach ($arg in $argArray) {
"#$((++$i)): [$arg]"
}
'# > $handlerScript
# Construct a temp. *.reg file.
# Target the scope-appropriate root registrykey.
$rootKey = ('HKEY_LOCAL_MACHINE\Software\Classes', 'HKEY_CURRENT_USER\Software\Classes')[$currentUserOnly]
# Determine a temp. file path.
$tempFile = [IO.Path]::GetTempPath() + [IO.Path]::GetRandomFileName() + '.reg'
#"
Windows Registry Editor Version 5.00
[$rootKey\$schemeName]
#="URL:$schemeName"
"URL Protocol"=""
[$rootKey\$schemeName\DefaultIcon]
#="$pwshPathEscaped"
[$rootKey\$schemeName\shell]
#="open"
[$rootKey\$schemeName\shell\open\command]
; === Tweak the PowerShell command line here: ===
#="\"$pwshPathEscaped\" -ExecutionPolicy Bypass -NoProfile -NoExit -File \"$handlerScriptEscaped\" %1"
"# > $tempFile
# Import the *.reg file into the registry.
& {
$ErrorActionPreference = 'Continue'
reg.exe import $tempFile 2>$null
if ($LASTEXITCODE) { Throw "Importing with reg.exe failed: $tempFile"}
}
# Remove the temp. *.reg file.
Remove-Item -ErrorAction Ignore -LiteralPath $tempFile
# ---
# Sample invocation of the new protocol with 3 arguments:
$uri = "$schemeName`:one `"two & three`" four"
Write-Verbose -Verbose "Invoking the following URI: $uri"
Start-Process $uri
What I am trying to do is download 2 images from URL's and open them after download. Here's what I have:
#echo off
set files='https://cdn.suwalls.com/wallpapers/cars/mclaren-f1-gtr-42852-400x250.jpg','http://www.dubmagazine.com/home/media/k2/galleries/9012/GTR_0006_EM-2014-12-21_04_GTR_007.jpg'
powershell "(%files%)|foreach{$fileName='%TEMP%'+(Split-Path -Path $_ -Leaf);(new-object System.Net.WebClient).DownloadFile($_,$fileName);Invoke-Item $fileName;}"
Im getting 'Cannot find drive' A drive with the name 'https' cannot be found.
It's the Split-path command that is having problems but cant seem to find a solution.
You could get away with basic string manipulation but, if the option is available, I would opt for using anything else that is data aware. In your case you could use the [uri] type accelerator to help with these. I would also just opt for pure PowerShell instead of splitting between batch and PS.
$urls = 'https://cdn.suwalls.com/wallpapers/cars/mclaren-f1-gtr-42852-400x250.jpg',
'http://www.dubmagazine.com/home/media/k2/galleries/9012/GTR_0006_EM-2014-12-21_04_GTR_007.jpg'
$urls | ForEach-Object{
$uri = [uri]$_
Invoke-WebRequest $_ -OutFile ([io.path]::combine($env:TEMP,$uri.Segments[-1]))
}
Segments will get you the last portion of the url which is a proper file name in your case. Combine() will build the target destination path for you. Feel free to add you invoke item logic of course.
This also lacks error handling if the url cannot be accessed or what not. So be aware of that possibility. The code above was meant to be brief to give direction.