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:
Related
I'm a beginner at Powershell and am struggling to understand some syntax from some code I found on Github. I've read the docs on Powershell assignment, and on switch statements, and can't understand what is going on with the = $Yes and = $No in this code snippet:
Switch ($Prompt3) {
Yes {
Stop-EdgePDF
Write-Output "Edge will no longer take over as the default PDF viewer."; = $Yes
}
No {
= $No
}
}
I haven't been able to find any references to this kind of syntax, and it doesn't seem to do anything in the script. So why is it there?
UPDATE: This issue has been resolved.
Looks to me like the variable name that was getting the assignment was deleted in a change back in August.
$PublishSettings = $Yes
Was changed to:
= $Yes
And:
$PublishSettings = $No
Was changed to:
= $No
Looks like poor search and replace.
I've created an issue for the problem at GitHub.
There are many characters that are valid in a function (or variable) name; this includes the = symbol. What you're observing is a function or alias.
Examples:
# standard function
function =
{
return $args
}
# accessing the function: drive
${Function:=} = {
return $args
}
# defining a new alias
New-Alias -Name = -Value Get-Variable
# using the Alias attribute
function Test-Thing
{
[Alias('=')]
param()
return $args
}
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.
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 }
I'm trying to pass an array of custom objects to a function for further processing of these objects.
Here's the function where I create my custom object array:
Function GetNetworkAdapterList
{
# Get a list of available Adapters
$hnet = New-Object -ComObject HNetCfg.HNetShare
$netAdapters = #()
foreach ($i in $hnet.EnumEveryConnection)
{
$netconprop = $hnet.NetConnectionProps($i)
$inetconf = $hnet.INetSharingConfigurationForINetConnection($i)
$netAdapters += New-Object PsObject -Property #{
Index = $index
Guid = $netconprop.Guid
Name = $netconprop.Name
DeviceName = $netconprop.DeviceName
Status = $netconprop.Status
MediaType = $netconprop.MediaType
Characteristics = $netconprop.Characteristics
SharingEnabled = $inetconf.SharingEnabled
SharingConnectionType = $inetconf.SharingConnectionType
InternetFirewallEnabled = $inetconf.InternetFirewallEnabled
SharingConfigurationObject = $inetconf
}
$index++
}
return $netAdapters
}
Then in my main code I call above function like this:
$netAdapterList = GetNetworkAdapterList
The $netAdapterList returns the expected data, and I can do stuff like:
$netAdapterList | fl Name, DeviceName, Guid, SharingEnabled
So far so good.
Now I want to call a function passing in the $netAdapterList
I've created a dummy function like this:
Function ShowAdapters($netAdapterListParam)
{
$netAdapterListParam | fl Name, DeviceName, Guid, SharingEnabled
}
And when I invoke it like this:
ShowAdapters $netAdapterList
Nothing gets printed out.
I've tried changing the function's signature but still no luck:
Function ShowAdapters([Object[]]$netAdapterListParam)
Function ShowAdapters([Object]$netAdapterListParam)
Function ShowAdapters([PSObject[]]$netAdapterListParam)
Function ShowAdapters([array]$netAdapterListParam)
Anybody knows what I'm doing wrong? How can I get to my custom objects inside the function?
Thanks for your reply #Christian. Tried your steps, copy pasting bits and pieces into the shell and it indeed worked. However If i run the full .ps1 script is not printing out anything.
I've run the script in Powershell IDE setting breakpoints inside the ShowAdapters function, and $netAdapterListParam has indeed the expected custom objects array I'm passing in, so I've narrowed down the issue to be in the FL commandlet.
For some reason $netAdapterList | fl Name, DeviceName, Guid, SharingEnabled did not work for me, so I ended up using the following instead:
$formatted = $netAdapterListParam | fl Name, DeviceName, Guid, SharingEnabled | Out-String
Write-Host $formatted
That did the trick and the 4 properties were printed on the screen.
Lessons learned:
1) The Powershell IDE built into Win7 can be a very useful tool for debugging scripts
2) Format-List can be quirky when formatting custom objects, so Out-String was required.
Is there a PowerShell command to list all previously loaded variables?
I am running some PowerShell scripts in Visual Studio and would like to list all variables that are available in the current PowerShell session.
I have tried the command:
ls variable:*;
But it returns the following:
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
ls variable:* should work, or Get-Variable. If these are resulting in bad output, it's due to a poorly-implemented host, not with powershell itself. If you open the standard console host (run powershell.exe), you will see that these work fine.
If you need to work around a bad host, you might have better luck dumping everything to explicit strings:
Get-Variable | Out-String
or
Get-Variable |%{ "Name : {0}`r`nValue: {1}`r`n" -f $_.Name,$_.Value }
Interestingly, you can just type variable, and that works too!
I figured this out because I was curious as to what ls variable:* was doing. Get-Help ls tells us that it's an alias for PowerShell's Get-ChildItem, which I know will list all of the children of an Object. So, I tried just variable, and voila!
Based on this and this, it seems that what ls variable:* is doing is telling it to do some sort of scope/namespace lookup using the * (all/any) wildcard on the variable list, which, in this case, seems extraneous (ls variable:* == ls variable: == variable).
I frequently noodle around, testing concepts in an interactive shell, but sometimes forget my variable names or what-all I have defined.
Here is a function I wrote to wrap Get-Variable, automatically excluding any globals that were defined when the shell started up:
function Get-UserVariable()
{
[CmdletBinding()]
param(
[Parameter(Position = 0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)][String[]]$Name,
[Parameter()][Switch]$ValueOnly,
[Parameter()][String[]]$Include,
[Parameter()][String[]]$Exclude,
[Parameter()][String]$Scope
)
$varNames = $global:PSDefaultVariables + #("PSDefaultVariables")
$gvParams = #{'Scope' = "1"}
if ($PSBoundParameters.ContainsKey('Name'))
{
$gvParams['Name'] = $Name
}
if ($PSBoundParameters.ContainsKey('ValueOnly'))
{
$gvParams['ValueOnly'] = $ValueOnly
}
if ($PSBoundParameters.ContainsKey('Include'))
{
$gvParams['Include'] = $Include
}
if ($PSBoundParameters.ContainsKey('Exclude'))
{
# This is where the magic happens, folks
$gvParams['Exclude'] = ($Exclude + $varNames) | Sort | Get-Unique
}
else
{
$gvParams['Exclude'] = $varNames
}
if ($PSBoundParameters.ContainsKey('Scope'))
{
$gvParams['Scope'] = $Scope
}
gv #gvParams
<#
.SYNOPSIS
Works just like Get-Variable, but automatically excludes the names of default globals.
.DESCRIPTION
Works just like Get-Variable, but automatically excludes the names of default globals, usually captured in the user's profile.ps1 file. Insert the line:
$PSDefaultVariables = (Get-Variable).name |% { PSEscape($_) }
...wherever you want to (either before, or after, any other stuff in profile, depending on whether you want it to be excluded by running this command.)
.PARAMETER Name
(Optional) Refer to help for Get-Variable.
.PARAMETER ValueOnly
(Optional) Refer to help for Get-Variable.
.PARAMETER Include
(Optional) Refer to help for Get-Variable.
.PARAMETER Exclude
(Optional) Refer to help for Get-Variable; any names provided here will be added to the existing list stored in $PSDefaultVariables (sorted / unique'd to eliminate duplicates.)
.PARAMETER Scope
(Optional) Refer to help for Get-Variable. The only asterisk here is that the default value is "1" just to get us out of this function's own scope, but you can override with whatever value you need.
.OUTPUTS
Refer to help for Get-Variable.
.EXAMPLE
PS> $foo = 1,2,3
PS> Get-UserVariable
Name Value
---- -----
foo {1, 2, 3}
#>
}
Set-Alias -Name guv -Value Get-UserVariable
Put the following line somewhere in your profile.ps1 file, either at the very top or very bottom, depending on whether you want to automatically exclude any other variables that get defined every time you run an interactive shell:
# Remember the names of all variables set up to this point (used by Get-UserVariable function)
$PSDefaultVariables = (Get-Variable).name
Hope this is helpful!