Get the "plain" (end-user readable) name of UWP apps installed on a system - powershell

I'm using the following PowerShell script to retrieve and save to a text file the list of UWP apps on a system. It gets the ID, name (system name) and packagefamilyname.
In addition to the name, I'm looking for a way to retrieve the plain name of the app: for example, "OneNote" instead of "Microsoft.Office.OneNote". Ideally, this name would also be localized: for example, "Calculatrice" (on a French system) instead of "Microsoft.WindowsCalculator".
I found this list of info retrieved by Get-AppxPackage but nothing like an end-user readable name... I'm not very familiar this area of expertise. Any help would be appreciated.
$installedapps = get-AppxPackage
$ids = $null
foreach ($app in $installedapps)
{
try
{
$ids = (Get-AppxPackageManifest $app -erroraction Stop).package.applications.application.id
}
catch
{
Write-Output "No Id's found for $($app.name)"
}
foreach ($id in $ids)
{
$line = $app.Name + "`t" + $app.packagefamilyname + "!" + $id
echo $line
$line >> 'c:\temp\output.txt'
}
}
write-host "Press any key to continue..."
[void][System.Console]::ReadKey($true)

[Completed updated]
You can do this pretty easily in C#. You have to reference the correct WinMDs from the Windows SDK (the actual directories will change depending on SDK version):
C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.Foundation.FoundationContract\3.0.0.0\Windows.Foundation.FoundationContract.winmd
C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.Foundation.UniversalApiContract\6.0.0.0\Windows.Foundation.UniversalApiContract.winmd
If you can't build a stand-alone EXE and just want pure PowerShell, you might be able to reference the WinMDs %systemroot%\system32\winmetadata. The code is pretty simple (I avoided await since I don't know if PowerShell has that):
// using Windows.Management.Deployment;
static void Main(string[] args)
{
GetList();
}
static void GetList()
{
var pm = new PackageManager();
var packages = pm.FindPackagesForUser("");
foreach (var package in packages)
{
var asyncResult = package.GetAppListEntriesAsync();
while (asyncResult.Status != Windows.Foundation.AsyncStatus.Completed)
{
Thread.Sleep(10);
}
foreach (var app in asyncResult.GetResults())
{
Console.WriteLine(app.DisplayInfo.DisplayName);
}
}
}

I spent some time looking for this today, and finally came up with a solution that doesn't involve a page of code, or digging around in files/registry/etc. Put the below two lines in a script or function, and it will return PoSh-friendly output which you can then pipe into ForEach-Object, Where-Object, Sort-Object, Export-CSV, etc.
$PkgMgr = [Windows.Management.Deployment.PackageManager,Windows.Web,ContentType=WindowsRuntime]::new()
$PkgMgr.FindPackages() | Select-Object DisplayName -ExpandProperty Id
The .FindPackages() method also has an overload which takes a Family Name, but the docs lead me to believe it can only accept exact names, not wildcard matches. So unless you know exactly what you are looking for, I am guessing it is best to retrieve the list of all packages, and then do your own searches on that list.
The docs do say that this will return packages for all users, and that it requires admin/elevated rights to run.

Related

Accessing array outside of a PS function

I am having a hard time figuring out how to get the PSCustomObject/array out of the function. I have tried using $Global:ZipList as well as just passing the variables into an array directly w/o a custom object but no luck. The reason I need this, is I need to then loop through the array/list after I get the filenames and then was going to loop through this list and unzip each file and log it and process it based on the extension in the zip; this is to be used for multiple zips, so I can't predetermine the file extensions without grabbing the filenames in the zip into a list. I would just use a shell however some of the zips are password protected, haven't figured out how to pass a password scripted to the shell com unzip windows feature so stuck with 7z for now. Any help would be greatly appreciated! Thanks
Function ReadZipFile([string]$ZipFileName)
{
[string[]]$ReadZipFile = & 'C:\Program Files\7-Zip\7z.exe' l "$ZipFileName"
[bool]$separatorFound = $false
#$ZipList = #()
$ReadZipFile | ForEach-Object{
if ($_.StartsWith("------------------- ----- ------------ ------------"))
{
if ($separatorFound)
{
BREAK # Second separator; We're done!
}
$separatorFound = -not $separatorFound
}
else
{
if ($separatorFound)
{
[DateTime]$FileCreatedDate = [DateTime]::ParseExact($_.Substring(0, 19),"yyyy'-'MM'-'dd HH':'mm':'ss", [CultureInfo]::InvariantCulture)
[Int]$FileSize = [Int]"0$($_.Substring(26, 12).Trim())"
$ZipFileName = $_.Substring(53).TrimEnd()
$ZipList = [PSCustomObject] #{
ZipFileName=$ZipFileName
FileCreatedDate=$FileCreatedDate
FileSize=$FileSize}
}
}
}
}
$z = ReadZipFile $ZipFileName
$ZipList | Select-Object ZipFileName
To be able to select from array created in the function outside of it. I believe my if statements may be blocking the global variable feature when i tried using global:

Replace multi-line text(code) in file using powershell 2

I'm busy creating a Nuget package that will be used internally only which installs other packages being dependencies, Ninject being one of them.
After installation, a NinjectWebCommon.cs file is added to the App_Start folder of the project. My custom package requires that file to be modified in the following way: (only showing part of the code)
//code above removed...
using Ninject;
using Ninject.Web.Common;
//modified this part already - refer to block quote 1 on how i did it
using mylibrary.whatever;
//code below removed...
I managed to insert the line "using mylibrary.whatever" by using install.ps1 (as per the Nuget package convention) in the following way, albeit not that sophisticated (VERY little experience with powershell):
Blockquote 1:
$p = get-project;
$p = Split-path $p.filename;
$p += "\App_Start\NinjectWebCommon.cs";
(Get-Content $p) | foreach-object {$_ -replace "using Ninject.Web.Common;", "using Ninject.Web.Common;`r`n using mylibrary.whatever;"} | Set-Content $p;
Now that's quite ok for a 1 line addition.
My problem comes in with changing this section...
// code above removed...
private static void RegisterServices(IKernel kernel)
{
}
// code below removed...
to this...
// code above removed...
private static void RegisterServices(IKernel kernel)
{
kernel.Load(new mylibrarymodule());
}
// code below removed...
To complicate things a little more, this NinjectWebCommon.cs file could be changed in the same manner by x amount of internal Nuger packages. So
// code above removed...
private static void RegisterServices(IKernel kernel)
{
kernel.Load(new mylibrarymodule());
}
// code below removed...
could also become
// code above removed...
private static void RegisterServices(IKernel kernel)
{
kernel.Load(new mylibrarymodule());
kernel.Load(new myotherlibrarymodule());
kernel.Load(new anotherlibrarymodule());
}
// code below removed...
Any help would be appreciated as powershell is not one of my strong points...
Managed to eventually sort this out in my install.ps1 with a lot of T&E:
install.ps1 in my Nuget Package:
#Get the file:
$p = get-project;
$p = Split-path $p.filename;
$p += "\App_Start\NinjectWebCommon.cs";
# Do the first insert
$regex = new-object Text.RegularExpressions.Regex "using Ninject.Web.Common;", ('singleline');
set-content $p $regex.Replace((get-content $p) -join "`n", "using Ninject.Web.Common;`n`tusing mylibrary.whatever;")
# Do second insert
$regex = new-object Text.RegularExpressions.Regex "private static void RegisterServices\(IKernel kernel\)\s*\{", ('singleline','multiline');
set-content $p $regex.Replace((get-content $p) -join "`n", "private static void RegisterServices(IKernel kernel)`n`t`t{`n`t`t`tkernel.Load(new myclass_in_mylibrary.whatever());")
Credit to:
Matt Ward's answer here and
Stej's answer here
The next challenge is to allow a complete uninstallation of the file once it's been edited as Nuget doesn't allow an uninstallation if the file was modified... *sigh...
I'm quite new to ps as well and had to do some search and testing to come up with this:
$fileName="C:\tmp\YourFileName"
$searchString="Primary search string here" #In your case it's: "private static void RegisterServices(IKernel kernel)"
$searchStrFound=0
(Get-Content $fileName) |
Foreach-Object {
#Send each line to output
$_
if ($_ -match $searchString)
{
#We found the actual search string
$searchStrFound=1
}
if ($searchStrFound=1 -And $_ -match "\{")
#If search string is already found and current line is {
{
"Insert your text here"
"Insert Line 2 here"
"Insert whatever here..."
$searchStrFound=0 #Reset value to 0 so that you won't change anything else
}
} | Set-Content $fileName
I tried this with your
// code above removed...
private static void RegisterServices(IKernel kernel)
{
}
// code below removed...
example and it worked just fine.
I hope it helps
/ut
I would actually recommend the other way around: do not overwrite that file, create your own MyNinjectBindings and thru a readme file instruct the developer to add the call manually to your code so it registers your bindings.
This way, if your package is updated, MyNinjectBindings will also get updated but the custom code the developer may have added to NinjectWebCommon.cs remains there.
It is a less intrusive way and more flexible, my opinion.
Thanks

PowerShell Switch not working with hyphenation

I'm creating a series of new PSObjects, from a CSV import, and then adding them to $new. I'm using a switch to try and set the value for the "Notes" property, as the object is being created\added, and I've run into something 'hinky'.
When I run this...
$import = Import-Csv c:\somerandom.csv
$new = #()
foreach ($Item in $Import) {
$obj = New-Object PsObject -Property #{
Name = $item.Name
Description = $Item.Description
Quantity = $Item.Quantity
Vendor = $Item.Vendor
SubCategory = "Misc"
Notes = ""
}
switch ($obj.Name) {
"iPod" { $obj.Notes = "Burn with the rest of the Apple garbage"}
"nVidia GTX 780ti" { $obj.Notes = "Steal immediately!" }
default { $obj.Notes= "Sorry man... I have no idea what that is"}
}
$new += $obj
}
... it works as expected. All of the entries from $import, are recreated in $new, with the addition of my "SubCategory" and "Notes" noteproperties (iPod gets burn tag, 780ti slated to be stolen). But when I run with the following as the switch...
switch ($obj.Name) {
'SOFM090-107-01-PF-R' { $obj.Notes = "Burn with the rest of the Apple garbage"}
'M094-107-01-PF-R' { $obj.Notes = "Steal immediately!" }
default { $obj.Notes = "Sorry man... I have no idea what that is"}
}
... It sets all the entries to the 'default' setting on the switch. I tried running the switch with a non-hyphenated name for one entry, and a hyphenated entry for the other, and only the hyphenated version was set properly.
The above code is altered from the actual code, but it properly illustrates what I'm trying to do. I need to add a noteproperty that is based off a list of part numbers, and will fill in the "Notes" entry with a tag of my choosing.
I've tried it with single quotes, double quotes, using the -wildcard and replacing the switch hyphens with *'s, and putting the ` character in before the -'s. Nothing seems to be working.
There's nothing wrong with the code, so the problem must lie in the data. I verified that it works fine with a CSV file that has those exact hyphenated values in the "Name" column.
If the switch doesn't work with the hyphenated names, then the values being imported into the Name property don't match what you have in the switch statement. It's a good idea to always post the data you're working with, or a sample of it, because often that's the source of the problem. Even when it isn't, it helps other people understand what you're trying to accomplish and what your code does. Since we don't have the data, I can suggest a few likely possibilities:
You're manually typing the names into the switch statement, and they look like what's in the CSV, but don't actually match, e.g. you're confusing O with 0 because they look the same in the font you're working with. I'd have suspected something like an en-dash instead of a hyphen, but you say you tried replacing the hyphens with wildcards
You have trailing spaces
You're single-quoting the hyphenated names in the CSV file (Import-Csv only understands double quotes; single quotes would be included in the value).
Here are a couple of things you can try to help identify why the data doesn't match (separately, not both together):
Replace switch ($obj.Name) { with switch -regex ($obj.Name) {
Use the following code to show you exactly what PowerShell is seeing in the Name property for each item and which switch conditions are being executed:
Write-Host -NoNewline "[$($obj.Name)] "
switch ($obj.Name) {
'SOFM090-107-01-PF-R' {Write-Host 'burn'; $obj.Notes = "Burn with the rest of the Apple garbage"}
'M094-107-01-PF-R' {Write-Host 'steal'; $obj.Notes = "Steal immediately!"}
default {Write-Host 'sorry'; $obj.Notes = "Sorry man... I have no idea what that is"}
}
If you post the data, we'll probably be able to tell you exactly why it's not working. But I can pretty much guarantee you that if you're using that code, the problem is that the imported Name values that aren't being matched with the right Notes values are in some way not the same as what you have in the switch conditions.

How can I quickly find VMs with serial ports in PowerCLI

I have a script that takes about 15 minutes to run, checking various aspects of ~700 VMs. This isn't a problem, but I now want to find devices that have serial ports attached. This is a function I added to check for this:
Function UsbSerialCheck ($vm)
{
$ProbDevices = #()
$devices = $vm.ExtensionData.Config.Hardware.Device
foreach($device in $devices)
{
$devType = $device.GetType().Name
if($devType -eq "VirtualSerialPort")
{
$ProbDevices += $device.DeviceInfo.Label
}
}
$global:USBSerialLookup = [string]::join("/",$ProbDevices)
}
Adding this function adds an hour to the length of time the script runs, which is not acceptable. Is it possible to do this in a more efficient way? All ways I've discovered are variants of this.
Also, I am aware that using global variables in the way shown above is not ideal. I would prefer not to do this; however, I am adding onto an existing script, and using their style/formatting.
Appending to arrays ($arr += $newItem) in a loop doesn't perform well, because it copies all existing elements to a new array. This should provide better performance:
$ProbDevices = $vm.ExtensionData.Config.Hardware.Device `
| ? { $_.GetType().Name -eq 'VirtualSerialPort' } `
| % { $_.DeviceInfo.Label }

Use GetElementsByClassName in a script

I'm trying to write a PowerShell script to get the text within all the classes named "newstitle" from a website.
This is what I have:
function check-krpano {
$geturl=Invoke-WebRequest http://krpano.com/news/
$news=$geturl.parsedhtml.body.GetElementsByClassName("newstitle")[0]
Write-Host "$news"
}
check-krpano
It obviously needs much more tweaking, but so far, it doesn't work.
I managed to write an script using GetElementById, but I don't know the syntax for GetElementsByClassName, and to be honest, I haven't been able to find much information about it.
NOTE:
I've ticked the right answer to my question, but that's not the solution that I had chose to use in my script.
Although I was able to find the content within a tag containing a certain class, using 2 methods, they were much slower that searching for links.
Here is the output using Measure-Command:
Search for divs containing class 'newstitle' using parsedhtml.body -> 29.6 seconds
Search for devs containing class 'newstitle' using Allelements -> 10.4 seconds
Search for links which its element 'href' contains #news -> 2.4 seconds
So I have marked as useful the Links method answer.
This is my final script:
function check-krpano {
Clear-Host
$geturl=Invoke-WebRequest http://krpano.com/news
$news = ($geturl.Links |Where href -match '\#news\d+' | where class -NotMatch 'moreinfo+' )
$news.outertext | Select-Object -First 5
}
check-krpano
If you figure out how to get GetElementsByClassName to work, I'd like to know. I just ran into this yesterday and ran out of time so I came up with a workaround:
$geturl.ParsedHtml.body.getElementsByTagName('div') |
Where {$_.getAttributeNode('class').Value -eq 'newstitle'}
getElementsByClassName does not return an array directly but instead a proxy to the results via COM. As you have discovered, conversion to an array is not automatic with the [] operator. You can use the list evaluation syntax, #(), to force it to an array first so that you can access individual elements:
#($body.getElementsByClassName("foo"))[0].innerText
As an aside, conversion is performed automatically if you use the object pipeline, e.g.:
$body.getElementsByClassName("foo") | Select-Object -First 1
It is also performed automatically with the foreach construct:
foreach ($element in $body.getElementsByClassName("foo"))
{
$element.innerText
}
Cannot, for the life of me, get that method to work either!
Depending upon what you need back in the result though, this might help;
function check-krpano {
$geturl=Invoke-WebRequest http://krpano.com/news
$news=($geturl.Links|where href -match '\#news\d+')[0]
$news
}
check-krpano
Gives me back:
innerHTML : krpano 1.16.5 released
innerText : krpano 1.16.5 released
outerHTML : krpano 1.16.5 released
outerText : krpano 1.16.5 released
tagName : A
href : #news1165
You can use those properties directly of course, so if you only wanted to know the most recently released version of krpano, this would do it:
function check-krpano {
$geturl=Invoke-WebRequest http://krpano.com/news
$news=($geturl.Links|where href -match '\#news\d+')[0]
$krpano_version = $news.outerText.Split(" ")[1]
Write-Host $krpano_version
}
check-krpano
would return 1.16.5 at time of writing.
Hope that achieves what you wanted, albeit in a different manner.
EDIT:
This is a possibly a little faster than piping through select-object:
function check-krpano {
$geturl=Invoke-WebRequest http://krpano.com/news
($geturl.Links|where href -match '\#news\d+'|where class -notmatch 'moreinfo+')[0..4].outerText
}
I realize this is an old question, but I wanted to add an answer for anyone else who might be trying to achieve the same thing by controlling Internet Explorer using the COM object like such:
$ie = New-Object -com internetexplorer.application
$ie.navigate($url)
while ($ie.Busy -eq $true) { Start-Sleep -Milliseconds 100; }
I normally prefer to use Invoke-WebRequest as the original poster did, but I've found cases where it seemed like I needed a full-fledged IE instance in order to see all of the JavaScript-generated DOM elements even though I would expect parsedhtml.body to include them.
I found that I could do something like this to get a collection of elements by a class name:
$titles = $ie.Document.body.getElementsByClassName('newstitle')
foreach ($storyTitle in $titles) {
Write-Output $storyTitle.innerText
}
I observed the same really slow performance the original poster noted when using PowerShell to search the DOM, but using PowerShell 3.0 and IE11, Measure-Command shows that my collection of classes is found in a 125 KB HTML document in 280 ms.
It seems to work with PowerShell 5.1:
function check-krpano {
$geturl = Invoke-WebRequest -Uri "http://krpano.com/news/"
$news = $geturl.ParsedHtml.body.getElementsByClassName("newstitle")
Write-Host "$($news[0].innerHTML)"
}
check-krpano
Output:
krpano 1.20.6<SPAN class=smallcomment style="FLOAT: right"><A href="https://krpano.co
m/forum/wbb/index.php?page=Thread&postID=81651#post81651"><IMG class=icon16m src="../design/ico-forumlink
.png"> krpano Forum Link</A></SPAN>