PowerShell Test-Path is returning false always - powershell

I have two below arrays in powershell
$obj= ('Sales','Finance','config.ini')
$objPath= ('D:\Project','D:\Element','D:\Project')
now $obj can be a folder name or a file name
$objPath is the path where the respective positional $obj will reside
I want to check if that folder or file exist in the respective $objPath
my code:
foreach($element in $obj){
foreach($elementpath in $objPath){
Test-Path $element+'\'+$elementpath
}
}
But it is returning false everytime. Can anyone please suggest where I am doing wrong

I think you've written the statement backwards. When you run it'll check for paths like:
Sales\D:\Project
Sales\D:\Element
Sales\D:\Project
Finance\D:\Project
Finance\D:\Element
Finance\D:\Project
config.ini\D:\Project
config.ini\D:\Element
config.ini\D:\Project
That obviously doesn't look right. You can try a minor re-write like:
Along with reversing the variable references you may want to entertain using Join-Path (as a best practice) like below:
foreach($element in $obj){
foreach($elementpath in $objPath){
Test-Path (Join-Path $elementpath $element)
}
}
It will work with string concatenation like below:
foreach($element in $obj){
foreach($elementpath in $objPath){
Test-Path ($elementpath + '\' + $element)
}
}
Per one of the comments string expansion will also work:
foreach($element in $obj){
foreach($elementpath in $objPath){
Test-Path "$elementpath\$element"
}
}

Related

Incrementing variables in powershell

I'm new to PowerShell and am trying to create a script that goes through a csv file (simple name,value csv) and loads each new line in it as a variable and then runs a function against that set of variables.
I've had success at getting it to work for 1 variable by using the following code snippet:
Import-Csv -Path C:\something\mylist.csv | ForEach-Object {
New-Variable -Name $_.Name -Value $_.Value -Force
}
My csv looks like this:
name,value
RegKey1,"Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\LanmanWorkstation"
Basically it's a list of registry keys each named as RegKey# and then the path of that reg key is the intended value of the variable.
I'm currently playing around with the "Test-Path" cmdlet that just prints out true/false if the passed reg-key exists and then just prints out some text based on if it found the reg key or not.
That snippet looks like so:
Test-Path $RegKey1
IF ($LASTEXITCODE=0) {
Write-Output "It worked"
}
else {
Write-Output "It didn't work"
}
This works fine however what I'm trying to achieve is for powershell to run this cmdlet against each of the lines in the csv file - basically checking each reg key in it and then doing whatever specified to it.
What I'm trying to avoid is declaring hundreds of variables for every regkey I plan on using but instead have this one function that just runs through the csv and every time it runs, it increments the number next to the variable's name - RegKey1,RegKey2,RegKey3 etc.
Let me know if there's a way to do this in powershell or a better way of approaching this altogether. I also apologize in advance if I've not provided enough info, please do let me know.
You need to place your if statement in the Foreach-Object loop. This will also only work, if your variable all get the same name of $RegKey. To incriment, you may use the for loop.
Import-Csv -Path C:\something\mylist.csv | ForEach-Object {
New-Variable -Name $_.Name -Value $_.Value -Force
IF (Test-Path $RegKey1) {
Write-Output "It worked"
}
else {
Write-Output "It didn't work"
}
}
The if statement returns a boolean value of $true, or $false. So theres no need to use $LastExitCode by placing the Test-Path as the condition to evaluate for.
Alternatively, you can use the Foreach loop to accomplish the same thing here:
$CSV = Import-Csv -Path C:\something\mylist.csv
Foreach($Key in $CSV.Value){
$PathTest = Test-Path -Path $Key
if($PathTest) {
Write-Output "It worked"
} else {
Write-Output "It didn't work"
}
}
By iterating(reading through the list 1 at a time) through the csv only selecting the value(Reg Path), we can test against that value by assigning its value to the $PathTest Variable, to be evaluated in your if statement just like above; theres also no need to assign it to a variable and we can just use the Test-Path in your if statement like we did above as well for the same results.

Type of Variable not as expected in Powershell

This is a question of understanding.
I construct a Directory Structure using a string Variable $path. I append the name of the Directory I want to create, this works as expected. When the path is completed I need the complete Directory as System.IO.DirectoryInfo but assigning the Path in this way $Path = Get-Item $Path results in a string type.
$path = "c:\dir1"
If(-Not (Test-Path $path))New-Item -ItemType Directory -Path $path
$path = $path + "\dir2"
If(-Not (Test-Path $path))New-Item -ItemType Directory -Path $path
# Assigned to same variable
$path = Get-Item $path
echo $path.GetType() # = string
# assigned to different variable
$p_a_t_h = Get-Item $path
echo $p_a_t_h.GetType() # = System.IO.DirectoryInfo
# solution but not understood the behavior
[System.IO.DirectoryInfo]$path = Get-Item $path
echo $path.GetType() # = System.IO.DirectoryInfo
It took hours to find out this behavior and I couldn't find any documentation why this is - maybe because I don't know what to search for.
It is clear, that for appending something to a variable, the type of the variable is relevant, but a $path = ... is a "new" assignement and should have the type of the assigned value - at least in my eyes. In the languages I used so far a variable becomes the type of its value and is not converted to a type the variable had earlier or I define the type of a variable and get an error if assigned with wrong type.
Where is the error in my logic?
I think that somewhere in your code you did a left-side cast (on the variable, not the value) to [String], just like you did later in your sample with [System.IO.DirectoryInfo]$path.
The most common way that this happens: Parameters.
Is this taken from a function? Like:
function Invoke-MyThing {
param([String]$Path)
}
Why that matters
When you put the type on the variable, all values assigned to that variable receive that cast.
[String]$Value = 'Hello'
$Value.GetType()
$Value = 3.141
$Value.GetType()
Casting the value only affects that one value:
$V2 = 5
$V2.GetType()
$V2 = [String]9
$V2.GetType()
$V2 = 45
$V2.GetType()
So, remove your previous variable-side cast, or if it's a parameter, just use a different local variable.
Better yet, if it's a parameter, you could make it of type [System.IO.DirectoryInfo] instead.. then it would accept that directly, or even accept a string. You just have to rework your code a little bit to deal with it.

Renaming one file (and nothing more than ONE file) using PowerShell

The problem
I constantly find myself in need of quick-method to rename a random file here and there while I work. I need to bring these filenames down to a structure compatible with web standards and some personal needs. A few examples below:
When I find I need
---------------------------------- -----------------------------------------
Welcome to the party.JPG welcome_to_the_party.jpg
Instructions (and some other tips) instructions_and_some_other_tips
Bar Drinks – The Best Recipes bar_drinks_the_best_recipes
La mañana del águila y el ratón la_manana_del_aguila_y_el_raton
Basically I need:
all uppercase characters to become lowercase
spaces to become underscore
some other special characters and diacritics for other languages to become their closest match (á is a, é is e, ç is c, and so on...)
Symbols like ( ) [ ] { } ' ; , to completely dissapear
Perhaps some replacements (optional) as: # = no; # = at or & = and
Not the question, but just FYI and you can see the big picture
I will be using a registry entry [HKEY_CLASSES_ROOT*\shell...] so I can call a batch file and/or a PowerShell Script by right-clicking the desired file, passing the argument information (the file in question) to the script that way.
My guesses
I have been looking closely at PowerShell Scripts, but I am not very knowledgeable about this area yet and all the solutions provided so far are addressing the entire folder (Dir/Get-ChildItem) instead of a specific file.
For example, I was successful using the line below (PowerShell) to replace all spaces by underscore, but then it affects other files in the directory as well.
Dir | Rename-Item –NewName { $_.name –replace “ “,”_“ }
Again, I do not need to address this problem for the entire folder, since I already have ways of doing so using software like Total Commander.
Thanks for any help you can give me.
Ruy
may be this code can help you
function Remove-Diacritics([string]$String)
{
$objD = $String.Normalize([Text.NormalizationForm]::FormD)
$sb = New-Object Text.StringBuilder
for ($i = 0; $i -lt $objD.Length; $i++) {
$c = [Globalization.CharUnicodeInfo]::GetUnicodeCategory($objD[$i])
if($c -ne [Globalization.UnicodeCategory]::NonSpacingMark) {
[void]$sb.Append($objD[$i])
}
}
return("$sb".Normalize([Text.NormalizationForm]::FormC))
}
function Clean-String([string]$String)
{
return(Remove-Diacritics ($String.ToLower() -replace "#", "no" -replace "\#", "at" -replace "&", "and" -replace "\(|\)|\[|\]|\{|\}|'|;|\,", "" -replace " ", "_"))
}
$youfile="C:\tmp4\121948_DRILLG.tif"
$younewnamefile=Clean-String $youfile
Rename-Item -Path $youfile $younewnamefile
Place this script somewhere (let's call it WebRename.ps1):
$old = $args -join ' '
$new = $old.ToLower().Replace(' ', '_')
# add all the remaining transformations you need here
Rename-Item $old $new
In the registry use this as the command (with your own path of course):
PowerShell -c C:\WebRename.ps1 "%1"
If your looking to be able to do this quickly and always want the same changes to be made you can add the following function to a .psm1 file and then place the file in one of your module folders (C:\Program Files\WindowsPowerShell\Modules is the most common one) you'll be able to just call WebRename-File filePath any time you need to quickly rename a file, the function is set up in such a way as to work fine if you pass in a single file path or you can pipe the results of a get-childitem to it if you ever do find the need to do bulk renames.
function WebRename-File {
param(
[parameter(Mandatory=$true,ValueFromPipeline=$true)]
$filePath
)
begin{}
Process{
foreach($path in $filePath){
$newPath = $path.ToLower()
$newPath = $newPath.Replace(' ','_')
###add other operations here###
Rename-Item -Path $path -NewName $newPath
}
}
end{}
}

Get actual path from path with wildcard

When using Test-Path in an if statement, I am looking to get the path that the if statement succeeds with.
For example, these files exist in C:
C:\Test6_1_15.txt
C:\Test6_2_15.txt
C:\Test6_3_15.txt
C:\Test6_4_15.txt
what do I do in the "then" branch?
$Path = "C:\Test6_*_15.txt"
if (Test-Path $Path)
{
# if test passes because there are 4 files that fit the test, but I want to be
# able to output the file that made the if statement succeed.
}
Sounds like you want Resolve-Path:
if(($Paths = #(Resolve-Path "C:\Test6_*_15.txt"))){
foreach($file in $Paths){
# do stuff
}
} else {
# Resolve-Path was unable to resolve "C:\Test6_*_15.txt" to anything
}
You can do get-item $path, that will return actual file name(s) in its result.
You will not get that with Test-Path. Test-Path returns a boolean value(s) representing the presence of the path(s) passed. Looking at the description from TechNet
It returns TRUE ($true) if all elements exist and FALSE ($false) if any are missing
If you just want the actual filenames that match then use Get-Item as it supports standard wildcards. You can get information from the System.IO.FileInfo objects that Get-Item returns.

Function return value in PowerShell

I have developed a PowerShell function that performs a number of actions involving provisioning SharePoint Team sites. Ultimately, I want the function to return the URL of the provisioned site as a String so at the end of my function I have the following code:
$rs = $url.ToString();
return $rs;
The code that calls this function looks like:
$returnURL = MyFunction -param 1 ...
So I am expecting a String, however it's not. Instead, it is an object of type System.Management.Automation.PSMethod. Why is it returning that type instead of a String type?
PowerShell has really wacky return semantics - at least when viewed from a more traditional programming perspective. There are two main ideas to wrap your head around:
All output is captured, and returned
The return keyword really just indicates a logical exit point
Thus, the following two script blocks will do effectively the exact same thing:
$a = "Hello, World"
return $a
 
$a = "Hello, World"
$a
return
The $a variable in the second example is left as output on the pipeline and, as mentioned, all output is returned. In fact, in the second example you could omit the return entirely and you would get the same behavior (the return would be implied as the function naturally completes and exits).
Without more of your function definition I can't say why you are getting a PSMethod object. My guess is that you probably have something a few lines up that is not being captured and is being placed on the output pipeline.
It is also worth noting that you probably don't need those semicolons - unless you are nesting multiple expressions on a single line.
You can read more about the return semantics on the about_Return page on TechNet, or by invoking the help return command from PowerShell itself.
This part of PowerShell is probably the most stupid aspect. Any extraneous output generated during a function will pollute the result. Sometimes there isn't any output, and then under some conditions there is some other unplanned output, in addition to your planned return value.
So, I remove the assignment from the original function call, so the output ends up on the screen, and then step through until something I didn't plan for pops out in the debugger window (using the PowerShell ISE).
Even things like reserving variables in outer scopes cause output, like [boolean]$isEnabled which will annoyingly spit a False out unless you make it [boolean]$isEnabled = $false.
Another good one is $someCollection.Add("thing") which spits out the new collection count.
With PowerShell 5 we now have the ability to create classes. Change your function into a class, and return will only return the object immediately preceding it. Here is a real simple example.
class test_class {
[int]return_what() {
Write-Output "Hello, World!"
return 808979
}
}
$tc = New-Object -TypeName test_class
$tc.return_what()
If this was a function the expected output would be
Hello World
808979
but as a class the only thing returned is the integer 808979. A class is sort of like a guarantee that it will only return the type declared or void.
As a workaround I've been returning the last object in the array that you get back from the function... It is not a great solution, but it's better than nothing:
someFunction {
$a = "hello"
"Function is running"
return $a
}
$b = someFunction
$b = $b[($b.count - 1)] # Or
$b = $b[-1] # Simpler
All in all, a more one-lineish way of writing the same thing could be:
$b = (someFunction $someParameter $andAnotherOne)[-1]
I pass around a simple Hashtable object with a single result member to avoid the return craziness as I also want to output to the console. It acts through pass by reference.
function sample-loop($returnObj) {
for($i = 0; $i -lt 10; $i++) {
Write-Host "loop counter: $i"
$returnObj.result++
}
}
function main-sample() {
$countObj = #{ result = 0 }
sample-loop -returnObj $countObj
Write-Host "_____________"
Write-Host "Total = " ($countObj.result)
}
main-sample
You can see real example usage at my GitHub project unpackTunes.
The existing answers are correct, but sometimes you aren't actually returning something explicitly with a Write-Output or a return, yet there is some mystery value in the function results. This could be the output of a builtin function like New-Item
PS C:\temp> function ContrivedFolderMakerFunction {
>> $folderName = [DateTime]::Now.ToFileTime()
>> $folderPath = Join-Path -Path . -ChildPath $folderName
>> New-Item -Path $folderPath -ItemType Directory
>> return $true
>> }
PS C:\temp> $result = ContrivedFolderMakerFunction
PS C:\temp> $result
Directory: C:\temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2/9/2020 4:32 PM 132257575335253136
True
All that extra noise of the directory creation is being collected and emitted in the output. The easy way to mitigate this is to add | Out-Null to the end of the New-Item statement, or you can assign the result to a variable and just not use that variable. It would look like this...
PS C:\temp> function ContrivedFolderMakerFunction {
>> $folderName = [DateTime]::Now.ToFileTime()
>> $folderPath = Join-Path -Path . -ChildPath $folderName
>> New-Item -Path $folderPath -ItemType Directory | Out-Null
>> # -or-
>> $throwaway = New-Item -Path $folderPath -ItemType Directory
>> return $true
>> }
PS C:\temp> $result = ContrivedFolderMakerFunction
PS C:\temp> $result
True
New-Item is probably the more famous of these, but others include all of the StringBuilder.Append*() methods, as well as the SqlDataAdapter.Fill() method.
You need to clear output before returning. Try using Out-Null. That's how powershell return works. It returns not the variable you wanted, but output of your whole function. So your example would be:
function Return-Url
{
param([string] $url)
. {
$rs = $url.ToString();
return
} | Out-Null
return $rs
}
$result = Return-Url -url "https://stackoverflow.com/questions/10286164/function-return-value-in-powershell"
Write-Host $result
Write-Host $result.GetType()
And result is:
https://stackoverflow.com/questions/10286164/function-return-value-in-powershell
System.String
Credits to https://riptutorial.com/powershell/example/27037/how-to-work-with-functions-returns
It's hard to say without looking at at code. Make sure your function doesn't return more than one object and that you capture any results made from other calls. What do you get for:
#($returnURL).count
Anyway, two suggestions:
Cast the object to string:
...
return [string]$rs
Or just enclose it in double quotes, same as above but shorter to type:
...
return "$rs"
Luke's description of the function results in these scenarios seems to be right on. I only wish to understand the root cause and the PowerShell product team would do something about the behavior. It seems to be quite common and has cost me too much debugging time.
To get around this issue I've been using global variables rather than returning and using the value from the function call.
Here's another question on the use of global variables:
Setting a global PowerShell variable from a function where the global variable name is a variable passed to the function
The following simply returns 4 as an answer. When you replace the add expressions for strings it returns the first string.
Function StartingMain {
$a = 1 + 3
$b = 2 + 5
$c = 3 + 7
Return $a
}
Function StartingEnd($b) {
Write-Host $b
}
StartingEnd(StartingMain)
This can also be done for an array. The example below will return "Text 2"
Function StartingMain {
$a = ,#("Text 1","Text 2","Text 3")
Return $a
}
Function StartingEnd($b) {
Write-Host $b[1]
}
StartingEnd(StartingMain)
Note that you have to call the function below the function itself. Otherwise, the first time it runs it will return an error that it doesn't know what "StartingMain" is.