PowerShell: Pass local variable to function - powershell

I have the following Powershell code:
function readConfigData
{
$workingDir = (Get-Location).Path
$file = ""
if ($Global:USE_LOCAL_SERVER)
{
$file = $workingDir + '\Configs\Localhost.ini'
}
else
{
$file = $workingDir + '\Configs\' + $env:COMPUTERNAME + '.ini'
}
Write-Host 'INIFILE: ' $file
if (!$file -or ($file = ""))
{
throw [System.Exception] "Ini fil är inte satt."
}
if (!(Test-Path -Path $file))
{
throw [System.Exception] "Kan inte hitta ini fil."
}
}
readConfigData
How should I declare the local variable $file that can be passed to the function Test-Path.
My local variable $file get populated but then when I place it as argument to other function it's like it is out of scope.
I read the about scopes article but wasn't able to figure it out.
Currently I get the error:
INIFILE: D:\Projects\scripts\Configs\HBOX.ini Test-Path : Cannot bind argument to
parameter 'Path' because it is an empty string. At
D:\Projects\freelancer.com\nero2000\cmd script to
powershell\script.ps1:141 char:27
+ if (!(Test-Path -Path $file))
+ ~~~~~
+ CategoryInfo : InvalidData: (:) [Test-Path], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Microsoft.PowerShell.Commands.TestPathCommand

if (!$file -or ($file = ""))
should be replaced by
if (!$file -or ($file -eq ""))
You assign $file to an empty string in the first if clause and therefore your variable is empty in the Test-Path call.
Edit: Also there are some alternatives: How can I check if a string is null or empty in PowerShell?
you could either use
if([string]::IsNullOrEmpty($file))
or even just
if(!$file)

As others have mentioned, you are unintentionally assigning a blank string to $file in your first if (!$file ... statement. That is really the root of your problem.
However, instead of:
if (!$file -or ($file = ""))
You could use this forumula, which I find explains itself better:
if([String]::IsNullOrEmpty($file))

I would define a function Get-ConfigFile to retrieve the config and add a switch for local server:
function Get-ConfigFile
{
Param(
[switch]$UseLocalServer
)
$workingDir = (Get-Location).Path
if ($UseLocalServer.IsPresent)
{
Join-Path $workingDir '\Configs\Localhost.ini'
}
else
{
Join-Path $workingDir ('\Configs\{0}.ini' -f $env:COMPUTERNAME)
}
}
I would also use the Join-Path cmdlet to join a path instead of string concatenations.
Now you can retrive the config file path using:
$configFile = Get-ConfigFile -UseLocalServer:$Global:USE_LOCAL_SERVER
And if needed, ensure that the file exists:
if (-not(Test-Path -Path $configFile))
{
throw [System.Exception] "Kan inte hitta ini fil."
}
Note:
Get-Location will give you the current powershell path (working location), if you want to get the path where your script is located, use this instead:
$workingDir = split-path -parent $MyInvocation.MyCommand.Definitio

Related

check for text in a file on a remote machine

I am using a powershell to query a file on a remote machines C-drive and if the file exist with status 'imaging completed' it should run other checks.
$filetofind = Get-Content C:\Image.log
#Get the list down to just imagestatus and export
foreach ($line in $filetofind)
{
$file = $line.trim("|")
echo $file >> C:\jenkins\imagestatus.txt
}
But when I run below commands I am getting the error.
Can anyone help ?
Get-Content : Cannot find path 'C:\Image.log' because it does not exist.
At line:18 char:15
+ $filetofind = Get-Content C:\Image.log
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Image.log:String) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Test-Path will check if a file exists, and Select-String can be used to search the file for a string, using the -Quiet param will make the command return True if the string is found, rather than returning each line in the text file that includes the string.
Then using both commands in simple if statements to check their status:
$file = "C:\Image.log"
$searchtext = "imaging completed"
if (Test-Path $file)
{
if (Get-Content $file | Select-String $searchtext -Quiet)
{
#text exists in file
}
else
{
#text does not exist in file
}
}
else
{
#file does not exist
}
EDIT:
To check the file on multiple computers you need to use a foreach loop to run the code against each computer separately. The below assumes you have one hostname per line in hostnames.txt.
$hostnames = Get-Content "C:\hostnames.txt"
$searchtext = "imaging completed"
foreach ($hostname in $hostnames)
{
$file = "\\$hostname\C$\GhostImage.log"
if (Test-Path $file)
{
if (Get-Content $file | Select-String $searchtext -quiet)
{
Write-Host "$hostname: Imaging Completed"
}
else
{
Write-Host "$hostname: Imaging not completed"
}
}
else
{
Write-Host "$hostname: canot read file: $file"
}
}

Moving files to trashbin

I have a direct delete function (deletes the files direct) implemented in my script, and a trashbin delete function (moving it to the trashbin first).
The problem is that the trashbin delete doesn't work. I've tried this suggested post already but it doesn't seem to work.
My full script :
## Top of the script
param(
[Parameter(Mandatory=$true)]
[ValidateRange(0,99999)]
[int]$minutes,
[Parameter(Mandatory=$true)]
[ValidateScript({Test-Path $_})]
[string]$maplocation,
[Parameter(Mandatory=$true)]
[ValidateSet("Direct", "TrashBin")]
[string]$consequence
)
## error notifications
## Variables
$file = Get-ChildItem -Path $maplocation | Get-Date
$time = Get-Date
$minutesconvert = (New-Timespan -Start $file -End $time).TotalMinutes
foreach ($file in $files)
{
if ($minutes -lt $minutesconvert -and $consequence -eq "direct")
{
Write-Verbose "File Found $file" -Verbose
Write-Verbose "Deleting $file" -Verbose
Remove-Item $file.FullName
}
elseif ($minutes -lt $minutesconvert -and $consequence -eq "trashbin")
{
Add-Type -AssemblyName Microsoft.VisualBasic
Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile($maplocation, 'OnlyErrorDialogs', 'SendToRecycleBin')
}
else
{
Write-Verbose -message "txt" -verbose
}
}
Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile($maplocation, 'OnlyErrorDialogs', 'SendToRecycleBin')
Error code in PowerShell console:
New-TimeSpan : Cannot convert 'System.Object[]' to the type 'System.DateTime' required
by parameter 'Start'. The method is not supported.
At C:\Users\david\Desktop\nieuw.ps1:21 char:39
+ $minutesconvert = (New-TimeSpan -Start <<<< $file -End $time).TotalMinutes
+ CategoryInfo : InvalidArgument: (:) [New-TimeSpan], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.NewTimeSpanCommand
This is your culprit:
$file = Get-ChildItem -Path $maplocation | Get-Date
The above statement will give you the current date and time for each file and folder in $maplocation. If $maplocation is not a single file the result is an array, which New-TimeSpan is not prepared to handle. The procedure is also very unlikely to be what you actually intended. You probably want the time difference between the last modification (creation) date of $maplocation (or its contents?). Besides, rather than calculating a timespan it's better to subtract the number of minutes from the current timestamp and use that as a reference date.
Also, depending on what you want to do in case $maplocation is a folder, you may need to process the item differently:
$maplocation is a folder and you want to delete the folder and everything in it, or $maplocation is a single file:
$maxAge = (Get-Date).AddMinutes(-$minutes)
$file = Get-Item $maplocation
if ($file.LastWriteTime -lt $maxAge) {
switch ($consequence) {
'direct' { ... }
'trashbin' { ... }
}
}
$maplocation is a folder and you want to delete only items from it that are older than the reference date:
$maxAge = (Get-Date).AddMinutes(-$minutes)
$files = Get-ChildItem $maplocation -Recurse
foreach ($file in $files) {
if ($file.LastWriteTime -lt $maxAge) {
switch ($consequence) {
'direct' { ... }
'trashbin' { ... }
}
}
}
Since the sample code from your question is incomplete further adjustments may be required.

Script Complains that File Cannot be Found When not Looking for it

I have the below function running in a logon script, which checks whether the user has the current version of IT Self Help.exe. If the current version is not present, then it should be copied onto the desktop from the $appsource folder:
function UrgentSupportApp {
$ErrorActionPreference = "Stop"
trap {Log-Error $_ $MyInvocation.MyCommand; Return}
$desktop = $env:USERPROFILE + '\Desktop\'
$apptarget = $desktop + 'IT Self Help.exe'
$appsource = '\\file\Administration\Unused\Apps\IT Support App\IT Self Help.exe'
# Remove the old version of the app "IT Help Request.exe"
$oldapps = Get-ChildItem $desktop -Filter *"Help Request.exe"
if ($oldapps.count -gt 0) {Remove-Item $oldapps.PSPath -Force}
# Copy the new version over if it is not already present
$currentversion = (Get-Command $appsource).FileVersionInfo.FileVersion
if (Test-Path $apptarget) {
if ((Get-Command $apptarget).FileVersionInfo.FileVersion -ne $currentversion) {
Copy-Item $appsource $desktop -Force ##### Line 981 #####
}
} else {
Copy-Item $appsource $desktop -Force
}
}
function Log-Error {
param (
$error,
[string]$sub,
$detail
)
$ErrorActionPreference = "Stop"
trap {Log-Error $_ $MyInvocation.MyCommand; Return}
$filename = "\\file\administration\Unused\LogonScriptErrors\$username - $sub - $computername - $(Get-Date -Format ddMMyyyy-HHmmss).log"
New-Item $filename -ItemType File -Value "Message: `r`n`t $($error.Exception.Message) `r`n `r`nPosition: `r`n`t $($error.InvocationInfo.PositionMessage) `r`n `r`nSub: `r`n`t $sub `r`n `r`nDetail: `r`n`t $detail"
}
For a couple of users, I am seeing this error come through on line 981, char 22 (see the comment above):
Could not find file 'C:\Users\USER.NAME\Desktop\IT Self Help.exe'.
At \\DC\NETLOGON\mainlogon.ps1:981 char:22
+ Copy-Item <<<< $appsource $desktop -Force
However
The file clearly can be found, as it made it through the fisrt If condition If (Test-Path $apptarget).
If the file couldn't be found, why would the script complain on that line, where we are not even looking for it?
What is this error trying to tell me? If the file could not be found, surely the script would just continue into the Else statement

Test if file exists in PowerShell

I can use Test-Path to check whether the file name entered exists, but I'd like to avoid producing a system error if the user hits RETURN and the input string is blank. I thought the -ErrorAction Common Parameter would do the trick, but this:
$configFile = Read-Host "Please specify a config. file: "
$checkfile = Test-Path $configFile -ErrorAction SilentlyContinue
still produces:
Test-Path : Cannot bind argument to parameter 'Path' because it is an empty string.
At C:\Scripts\testparm2.ps1:19 char:31
+ $checkfile = Test-Path <<<< $configFile -ErrorAction SilentlyContinue
+ CategoryInfo : InvalidData: (:) [Test-Path], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Microsoft.PowerShell.Commands.TestPathCommand
Do I have to check that the string isn't blank or NULL explicitly?
I'm using PowerShell v2.0
You can do something like this:
$checkfile = if ("$configFile") {
Test-Path -LiteralPath $configFile
} else {
$false
}
The double quotes prevent false negatives, e.g. in case you want to test for the existence of a folder named 0.
Another option would be to set $ErrorActionPreference. However, in that case you need to cast the result of Test-Path to a boolean value, because although the exception is suppressed the cmdlet still doesn't return a result. Casting that $null "return value" to bool produces $false.
$oldEAP = $ErrorActionPreference
$ErrorActionPreference = 'SilentlyContinue'
$checkfile = [bool](Test-Path -LiteralPath $configFile)
$ErrorActionPreference = $oldEAP
Yes, you have to check explicitly for string null or empty:
$configFile = Read-Host "Please specify a config. file: "
if ([string]::IsNullOrEmpty($configFile))
{
$checkfile = $false
}
else
{
$checkfile = Test-Path $configFile -ErrorAction SilentlyContinue
}
Or use try/catch:
$configFile = Read-Host "Please specify a config. file: "
if ( $(Try { Test-Path $configFile.trim() } Catch { $false }) )
{
$checkfile = $true
}
else
{
$checkfile = $false
}

Get datetaken attribute on file

I am trying to write a script that will get the DATETAKEN attribute from a photo and create a folder structure based on that and move the file to this new location. I have found scripts on google that I'm trying to use but when I running it, it returns:
PS C:\Temp> C:\Temp\MovePhoto.ps1
GAC Version Location
--- ------- -------- True v4.0.30319
C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Drawing\v4.0_4.0.0....
Move-Item : The process cannot access the file because it is being
used by another process. At C:\Temp\MovePhoto.ps1:43 char:5
+ Move-Item $FileFullName "$FileDirFull\$FileBaseNameNU"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (C:\NoBackup\TES...RA\IMG_1372.JPG:FileInfo) [Move- Item],
IOException
+ FullyQualifiedErrorId : MoveFileInfoItemIOError,Microsoft.PowerShell.Commands.MoveItemCommand
If I do the script without the SystemDrawing line it works. But then I can't get the DATETAKEN attribute. I just can't figure out what I am missing.
Here is the script
[reflection.assembly]::LoadWithPartialName("System.Drawing")
$FileAll = (Get-ChildItem $SourcePath -Recurse | where {!$_.psiscontainer} | Select-Object Name,Fullname,BaseName,Extension,CreationTime,LastWriteTime,Length,#{Name="MD5";Expression={Get-Md5Hash $_.fullname}} | group MD5 | Where {$_.Count -gt 1 } | %{$_.Group} | sort MD5)
foreach ($File in $FileAll) {
$FileBaseName = $File.BaseName
$FileExtension = $File.Extension
$FileFullName = $File.FullName
$FileBaseNameNu = $FileBaseName + $FileExtension
$FileName = $File.Name
}
$foo = New-Object -TypeName System.Drawing.Bitmap -ArgumentList $FileFullName
$date = $foo.GetPropertyItem(36867).Value[0..9]
$arYear = [Char]$date[0],[Char]$date[1],[Char]$date[2],[Char]$date[3]
$arMonth = [Char]$date[5],[Char]$date[6]
$arDay = [Char]$date[8],[Char]$date[9]
$strYear = [String]::Join("",$arYear)
$strMonth = [String]::Join("",$arMonth)
$strDay = [String]::Join("",$arDay)
$DateTaken = $strYear + "-" + $strMonth + "-" + $strDay
$FileLastWriteTime = $File.LastWriteTime
$FileDirYear = $FileLastWriteTime.Year
$FileDirDate = $FileLastWriteTime.ToShortDateString()
$FileDirFull = "$DestinationPath\DUBLETTER\$FileDirYear\$DateTaken"
# Create destination path
if ((Test-Path $FileDirFull) -eq $false) {
New-Item -Path $FileDirFull -ItemType Directory
}
if (Test-Path (Join-Path $FileDirFull $File.Name)) {
$n = 0
while ((Test-Path (Join-Path $FileDirFull $FileBaseNameNU)) -eq $true){
$FileBaseNameNU = $FileBaseName + "-" + ++$n + $FileExtension
}
}
Move-Item $FileFullName "$FileDirFull\$FileBaseNameNU"
}
Can you try to replace
[reflection.assembly]::LoadWithPartialName("System.Drawing")
by
Add-Type -AssemblyName "system.drawing"
Forget it, your trouble is with your file C:\NoBackup\TES...RA\IMG_1372.JPG wich can't be moved because it's open (seems to be open for usage in $foo var). Try first to copy it. You perhaps can use $foo.Dispose() before Move-Item $FileFullName "$FileDirFull\$FileBaseNameNU"
I've done this in VBScript, using GetDetailsOf()... see MSDN
This can be done using PowerShell, see... Scripting Guys
There may be a .NET method way of doing this, i.e.: more "native" to PS.