Variable not being handled as I expected in PowerShell...what am I doing wrong?

Ok, I have fussed with this long enough.. I need some help.  I am running a powershell script to which I am passing variables.  I am trying to pass a logfile location.  The script works fine when I call it as follows:

./Scriptname.ps1 -KillVMs

OR

./Scriptname.ps1 -KillVms -log=\\server\share\test.log

But if fails anytime I try and specify a log in a local directory as follows:

./Scriptname.ps1 -KillVMs -log=c:\temp\test.log

The value for logfile always gets set to c:.log.  Its like the :\ is screwing up the array in the function.  Can someone take a quick look and let me know what the heck I am missing.  You should be able to just save the script below as Scriptname.ps1 and run it with the above examples to see what I am talking about.  Thanks!
function Get-ScriptName(){
    $tmp = $MyInvocation.ScriptName.Substring($MyInvocation.ScriptName.LastIndexOf('\') + 1)  
    $tmp.Substring(0,$tmp.Length - 4)  
} 

function Check-Value($Param, $Values = $args) {
	foreach ($value in $Values){
		if ($value.contains("=")){
			$arrTmp = $value.Split("=")
			if ($arrTmp[0].ToLower() -eq $Param.ToLower()){
				return $arrTmp[1]
			}
		}else{
			if ($value -eq $Param){
				return $true
			}
		}
	}
}

if ((Check-Value "-KillVMs")){
	Write-Host "YES"
}else{
	Write-Host "NO"
}

$LogFile = Check-Value "-log"
if($LogFile.length -gt 0){
	if($LogFile.StartsWith("\\")){  
		Write-Host "Logfile is on UNC path"  
	}  
	elseif($LogFile.contains(":")){  
		Write-Host "Logfile is on Local storage"  
	}  
	else{  
		$LogFile = [System.IO.Path]::Combine((Get-Location), $LogFile)  
	}  
	  
	if($LogFile.EndsWith(".log") -eq $false){  
		$LogFile += ".log"  
	}
}else{
	$LogFile = ""
}
  
if($LogFile -eq ""){  
    #Set log file  
    $LogFile = [System.IO.Path]::Combine((Get-Location), (Get-ScriptName) + ".log")  
}  

 
write-host $LogFile

Open in new window

electricd7Asked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

chrismerrittCommented:
I've not fiddled with passing params into a script via the calling of the script, but i've taken your code and had a quick play around with it (sorry I modified it somewhat for my own readibility of it).

It seems you need to call the log directory parameter within quotes or it starts to split the values.

I added a section which echoes back the arguments so you can see what I mean:

Function Get-ScriptName()
{
    $tmp = $MyInvocation.ScriptName.Substring($MyInvocation.ScriptName.LastIndexOf('\') + 1)  
    $tmp.Substring(0,$tmp.Length - 4)  
} 

Function Check-Value($Param, $Values = $args)
{
	foreach ($value in $Values)
	{
		if ($value.contains("="))
		{
			$arrTmp = $value.Split("=")
			if ($arrTmp[0].ToLower() -eq $Param.ToLower())
			{
				return $arrTmp[1]
			}
		}
		else
		{
			if ($value -eq $Param)
			{
				return $true
			}
		}
	}
}

write-host "Arg count : $($args.count)"

for ($i = 0; $i -lt $args.count; $i++)
{
	$args[$i]
}

if ((Check-Value "-KillVMs"))
{
	Write-Host "YES"
}
else
{
	Write-Host "NO"
}

$LogFile = Check-Value "-log"
if ($LogFile.length -gt 0)
{
	if ($LogFile.StartsWith("\\"))
	{  
		Write-Host "Logfile is on UNC path"  
	}  
	elseif ($LogFile.contains(":"))
	{  
		Write-Host "Logfile is on Local storage"  
	}  
	else
	{  
		$LogFile = [System.IO.Path]::Combine((Get-Location), $LogFile)  
	}  
	  
	if ($LogFile.EndsWith(".log") -eq $false)
	{  
		$LogFile += ".log"  
	}
}
else
{
	$LogFile = ""
}
  
if ($LogFile -eq "")
{  
    #Set log file  
    $LogFile = [System.IO.Path]::Combine((Get-Location), (Get-ScriptName) + ".log")  
}  

write-host $LogFile

Open in new window


Wrong:

PS C:\TEMP> .\script.ps1 -KillVms -log=C:\server\share\test.log
Arg count : 3
-KillVms
-log=C:
\server\share\test.log
YES

Right:

PS C:\TEMP> .\script.ps1 -KillVms "-log=C:\server\share\test.log"
Arg count : 2
-KillVms
-log=C:\server\share\test.log
YES
Logfile is on Local storage
C:\server\share\test.log
0
Qlemo"Batchelor", Developer and EE Topic AdvisorCommented:
It is a bad idea to parse the commandline yourself (as you can see from your example). Just use the build-in capabilites of PS. If you put this line to the very beginning of your script:
param([Switch] $KillVMs, $log)

Open in new window

you can just use the vars $KillVMs (with $true or $false), and $log, to get your values. No further parsing needed:
write-host $(if ($KillVMs) {"Yes"} else {"No"})
write-host "Logfile is $log"

Open in new window

Calling syntax is
   .\Scriptname.ps1 -KillVMs -log c:\temp\test.log
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
electricd7Author Commented:
Yes but why is it splitting like it is as written. And why would it be a bad idea to pass variables this way?  I am really trying to understand why this is happening and not just change the code to make it work. I have hundreds of lines I have left out and many more parameters that I pass. The log is the only one that does this.
0
Has Powershell sent you back into the Stone Age?

If managing Active Directory using Windows Powershell® is making you feel like you stepped back in time, you are not alone.  For nearly 20 years, AD admins around the world have used one tool for day-to-day AD management: Hyena. Discover why.

chrismerrittCommented:
I would follow Qlemo's advice to be honest, seems to be the proper way to use parameters in your script that can be called from a command line.
0
Qlemo"Batchelor", Developer and EE Topic AdvisorCommented:
To be honest, I have not the slightest clue why $args collation is built that way (it is not the split doing that).

It is a bad idea since PowerShell applies some logic already. Not using what PS provides you with, and building another, incompatible system aside of it, is not worth the hassle.
Not to forget, $args is a very fragile thing. You do not get any help about parameters provided that way, and some validaty checks are omitted. $args should be used when you have an unknown amount of repeating parameters, like file names, only, and not for parsing switches. It is not effective, prone to errors, and again not worth it.
0
electricd7Author Commented:
Qlemo - Thanks for the explanation.  In your example above:

param([Switch] $KillVMs, $log)
write-host $(if ($KillVMs) {"Yes"} else {"No"})
write-host "Logfile is $log"

Open in new window


Which works great as written, would I just add the rest of my parameters to the declaration in the top line in order to add more?  ie:


 
param([Switch] $KillVMs, $log, $ExecuteA, $ExecuteB, etc., etc.)

Open in new window

0
electricd7Author Commented:
Actually when I try adding all my parameters to the first line, I get "Missing an argument for the parameter xxx" for most all of them??  Most of them are simply True/False, so I thought just having them specified would work?  I would like to be able to call the script with only the commands blocks I wish to run specified.  Ie, I have the following first line in my script:

 
param([Switch] $CheckPowerState, $RemoveVMs, $RemoveDatastores, $OfflineFlexClones, $DestoryFlexClones, $CreatePageFiles, $CreateFlexClones, $EditVMX, $RemoveCIFS, $MountDatastores, $AddVMs, $log)

Open in new window


And then for each block of code I begin with an IF statement like this:

 
if (($err -eq 0) -and ($RemoveVMs))

Open in new window


So I would like to be able to call the script like:

 .\Scriptname.ps1 -KillVMs -log c:\temp\test.log -CheckPowerState

And only have the script set the log file, and execute the 2 blocks of code specified by the parameters called.  
0
electricd7Author Commented:
Actually I think this seems to work, does this look correct:

 
param([String]$log, [Switch]$CheckPowerState = $false, [Switch]$RemoveVMs = $false, [Switch]$RemoveDatastores = $false, [Switch]$OfflineFlexClones = $false, [Switch]$DestoryFlexClones = $false, [Switch]$CreatePageFiles = $false, [Switch]$CreateFlexClones = $false, [Switch]$EditVMX = $false, [Switch]$RemoveCIFS = $false, [Switch]$MountDatastores = $false, [Switch]$AddVMs = $false)

Open in new window

0
Qlemo"Batchelor", Developer and EE Topic AdvisorCommented:
Correct. [Switch] is a type, und you need to provide it for every switch-like argument (which can only be $true or $false). And the other arguments should get types, too, as you have done for $log.

You do not need to provide $false as default - it is the default for [Switch].
0
electricd7Author Commented:
OK thanks again.  I have it working as I wanted it now.  Still learning with PowerShell, but really becoming a fan quickly!
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Powershell

From novice to tech pro — start learning today.