First parameter in PowerShell function loses its value? - powershell

Here's a function that I call with .\GetEMSInstallers. For some unknown reason the first parameter always loses its value:
function Get-EMSInstallers {
param (
$ems_for_amx_source = '\\server\ems_path',
$installers_dir = 'D:\installers'
)
process {
if (!(Test-Path "$installers_dir\EMS4AMX")) {
"Copying files and folders from $ems_for_amx_source to $installers_dir\EMS4AMX"
copy $ems_for_amx_source "$installers_dir\EMS4AMX" -rec -force
}
}
}
Get-EMSInstallers $args
When I call it I get this output:
Copying files and folders from to D:\installers\EMS4AMX
Copy-Item : Cannot bind argument to parameter 'Path' because it is an empty array.
At C:\Users\ad_ctjares\Desktop\Scripts\Ems\GetEMSInstallers.ps1:12 char:17
+ copy <<<< $ems_for_amx_source "$installers_dir\EMS4AMX" -rec -force
+ CategoryInfo : InvalidData: (:) [Copy-Item], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyArrayNotAllowed,Microsoft.PowerShell.Commands.CopyI
temCommand

When you don't pass in any argument to Get-EMSInstallers you still have a $args array - it is just empty. So the $ems_for_amx_source parameters is set to this empty array.
In other words, one way around this:
if ($args)
{
Get-EMSInstallers $args
}
else
{
Get-EMSInstallers
}
There is probably a more powershelly way to do this - I might revise this later if it comes to mind. :-) But that will get you started anyway.

Related

Add folder to zip

I am facing a problem how to add folder to existing ZIP file.
This zip file is created by PowerShell also.
I can only use system classes provided by Powershell 5. I cannot use any of user packages or plugins (7zip included).
Here is my code:
function addFileToArchiveTest ($filePathToAdd, $archivePathToUpdate) {
if ([System.IO.File]::Exists($filePathToAdd) -or (Test-Path $filePathToAdd)) {
$file = [System.IO.Path]::GetFileName($filePathToAdd);
Write-Host $filePathToAdd.Name;
Write-Host $filePathToAdd;
Write-Host $archivePathToUpdate;
$archive = [System.IO.Compression.ZipFile]::Open($archivePathToUpdate, [System.IO.Compression.ZipArchiveMode]::Update);
$compressionLevel = [System.IO.Compression.CompressionLevel]::NoCompression;
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($archive, $filePathToAdd, $file, "$compressionLevel");
$archive.Dispose();
} else {
Write-Host "[ERROR#function] <AddFileToArchive>: <filePathToAdd> does not exist!";
Write-Host "[ERROR#function] <Variable<filePathToAdd>>: $filePathToAdd";
Write-Host "[ERROR#function] <Variable<archivePathToUpdate>>: $archivePathToUpdate";
}
}
I am thinking about variable $file - there might be a problem, because folder doesn't have an extension.
I run script like this:
PS> addFileToArchiveTest "C:\TestFolder\FolderToArchive" "C:\TestFolder\thereIsAlreadyZipFile.zip"
It returns with error:
Exception calling "CreateEntryFromFile" with "4" argument(s): "Access to the
path 'C:\TestFolder\FolderToArchive' is denied."
At C:\Users\user\Desktop\testfolder.ps1:196 char:13
+ [System.IO.Compression.ZipFileExtensions]::CreateEntryFro ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : UnauthorizedAccessException
Noted I also try allow script and I am launching with admin rights.
Perhaps surprisingly, CreateEntryFromFile() is for adding files, not folders. You need to add each file individually:
Get-ChildItem $filePathToAdd | ForEach-Object {
[IO.Compression.ZipFileExtensions]::CreateEntryFromFile($archive, $_.FullName, $_.Name, "$compressionLevel")
}
As user #guiwhatsthat answered: PowerShell 5 does support Compress-Archive. It does exactly what you want.
That is working as I want.

PowerShell Native Cmdlet Set-Alias with Function and Switch

Question:
Is it possible to set an alias that calls both a function and a switch?
Here is an example I am working with.
Set-Alias -Name myidea -Value 'Test-FunctionIdea -SwitchIdea'
function Test-FunctionIdea {
param(
[switch]$SwitchIdea
)
if($SwitchIdea)
{
Write-Host 'Good Idea'
}
}
Here is the error I am having:
myidea : The term 'Test-FunctionIdea -SwitchIdea' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if
a path was included, verify that the path is correct and try again.
At line:1 char:1
+ myidea
+ ~~~~~~
+ CategoryInfo : ObjectNotFound: (Test-FunctionIdea -SwitchIdea:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
If it is possible please let me know how it's done. I've searched the internet for this and came up with no results.
You can check $MyInvocation.InvocationName and adjust the parameters inside the function:
function Some-Function([switch] $foo) {
if ($MyInvocation.InvocationName -eq 'foo') { $foo = $true }
}
Set-Alias foo Some-Function
The [usually negligible] advantage is that it doesn't create an additional execution context for the new function scope.

Why passing args from powershell gives error while working from inside script itself

Why calling .\MyScript.ps1 -Uninstall from Powershell gives an error
+ Super-Function $Args
+ ~~~~~
+ CategoryInfo : InvalidData : (:) [Super-Function], ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Super-Function
While calling "Super-Function" from the script itself with Super-Function -Uninstall , replacing $Args with the switch works ?
Why copy pasting the function on Powershell and then going for Super-Function -Uninstall works too ?
Here's the content of MyScript.ps1
function Super-Function([parameter(Mandatory=$False)][ValidateScript({Test-Path _$})][String]$var1 = ".\MyFile.ext",
[parameter(Mandatory=$False)][ValidateScript({Test-Path _$})][String]$var2 = "HKLM:\SOFTWARE\Wow6432Node\Publisher\SoftwareName",
[parameter(Mandatory=$False)][ValidateScript({([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")})][Switch]$Uninstall,
[parameter(Mandatory=$False)][ValidateScript({([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")})][Switch]$Install
)
{
}
Super-Function $Args
You have a few issues there that I see. Your ValidateScript for each argument have an issue. First, might just be a typo, you have the characters backwards for current pipe item. Should be $_ instead of _$. Next I find it befuddling that you test the presence of the admin role against a couple of boolean switches. Lets just move that inside the function (If what you had works that is fine. Just does make much sense)
Lastly, and most importantly, what you are trying to do with $args is called splatting. Use #args which will splat the hashtable of arguments, passed in from the script, against the function.
function Super-Function{
param(
[parameter(Mandatory=$False)][ValidateScript({Test-Path $_})][String]$var1 = ".\MyFile.ext",
[parameter(Mandatory=$False)][ValidateScript({Test-Path $_})][String]$var2 = "HKLM:\SOFTWARE\Wow6432Node\Publisher\SoftwareName",
[parameter(Mandatory=$False)][Switch]$Uninstall,
[parameter(Mandatory=$False)][Switch]$Install
)
# Use this to verify what has been assinged to your parameters. Will not show default values.
#$PSBoundParameters
If(([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")){
"Sure"
} Else {
Throw "Nope"
}
}
Super-Function #args

Unix Time conversion with powershell

Import-Module SQLite
mount-sqlite -name places -data (resolve-path C:\Users\malware_win7x86\Desktop\places.sqlite)
$places = Get-ChildItem places:\moz_places
foreach ($entry in $places)
{
[datetime]$origin = '1970-01-01 00:00:00'
$visited = $entry.last_visit_date
if ($visited) {
$vtime = $origin.AddSeconds($visited)
} else {
$vtime = 'testing'
}
write-host ""
$entry.url,
$entry.visit_count,
$vtime
}
Reference: Convert Unix time with PowerShell
I'm curious on how to handle entries with no data.
For example, here is some output from that script:
http://sysforensics.org/
1
Exception calling "AddSeconds" with "1" argument(s): "Value to add was out of range.
Parameter name: value"
At line:7 char:28
+ $vtime = $origin.AddSeconds <<<< ($visited)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
Any ideas on this?
Thanks
That error looks like you're giving AddSeconds an argument that is either too big or too small, rather than missing data. Calling AddSeconds with either $null or 0 will return the original date unchanged.
That being said—and I can't be sure, since you haven't provided the actual integer that you're trying to convert—I had the same problem until I realized that the data I was receiving was in milliseconds, not seconds. You can either use AddMilliseconds instead, or divide the number by 1000 before using AddSeconds if you don't care about the loss of precision.
Credit to this answer for pointing me in the right direction.
In that case you'd just skip the method call and substitute a default value, e.g.:
if ($visited) { # or maybe $ivisited -is [int] or whatever your criterion is
$vtime = $origin.AddSeconds($visited)
} else {
$vtime = $null
}

powershell: how to print the total call stacks when error happen?

suppose I have the following code, when the error happens, I'd like to see the error that the error first happened at function b, and then happened at function a. But in fact it only tells me the error happen at function a, since function a could be called many times, I don't know which outer function calling function a caused the problem
cls
function a{
Remove-Item "not-exist-item"
}
function b{
a
}
b
Remove-Item : Cannot find path 'C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\not-exis
t-item' because it does not exist.
At C:\Users\Daniel.Wu\AppData\Local\Temp\2\a.ps1:***3 char:14***
+ Remove-Item <<<< "not-exist-item"
+ CategoryInfo : ObjectNotFound: (C:\Program File...\not-exist-item:String) [Remove-Item], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand
If you are on PowerShell v2.0, use Get-PSCallStack. If you're still on v1, use a function like this:
function Get-CallStack {
trap { continue }
1..100 | foreach {
$var = Get-Variable -scope $_ MyInvocation
$var.Value.PositionMessage -replace "`n"
}
}
One option is to set
$ErrorActionPreference = 'inquire'
and then invoke the problematic script. On error you are prompted for choices, choose Suspend to enter into the nested prompt mode. Then type
Get-PSCallStack
or even
Get-PSCallStack | fl
to get the current call stack information.
Does get-help about_debuggers provide any illumination?