Windows PowerShell invoking function with parameters - function

I am totally new to PowerShell, and trying to write a simple script to produce log file. I searched forums and could not find the answer for my question.
I found the example in the net, that I thought would be useful, and applied it to my script:
## Get current date and time. In return, you’ll get back something similar to this: Sat January 25 10:07:25 2014
$curDateTime = Get-Date
$logDate = Get-Date -format "MM-dd-yyyy HH:mm:ss"
$LogPath = "C:\Temp\Log"
$LogName = "log_file_" + $logDate + ".log"
$sFullPath = $LogPath + "\" + $LogName
<#
param(
## The path to individual location files
$Path,
## The target path of the merged file
$Destination,
## Log path
$LogPath,
## Log name
$LogName
## Full LogFile Path
## $sFullPath = $LogPath + "\" + $LogName
)
#>
Function Log-Start {
<#
.SYNOPSIS
Creates log file
.DESCRIPTION
Creates log file with path and name that is passed.
Once created, writes initial logging data
.PARAMETER LogPath
Mandatory. Path of where log is to be created. Example: C:\Windows\Temp
.PARAMETER LogName
Mandatory. Name of log file to be created. Example: Test_Script.log
.INPUTS
Parameters above
.OUTPUTS
Log file created
#>
[CmdletBinding()]
Param ([Parameter(Mandatory=$true)][string]$LogPath, [Parameter(Mandatory=$true)][string]$LogName)
Process {
## $sFullPath = $LogPath + "\" + $LogName
# Create file and start logging
New-Item -Path $LogPath -Value $LogName –ItemType File
Add-Content -Path $sFullPath -Value "***************************************************************************************************"
Add-Content -Path $sFullPath -Value "Started processing at [$([DateTime]::Now)]."
Add-Content -Path $sFullPath -Value "***************************************************************************************************"
Add-Content -Path $sFullPath -Value ""
}
}
Set-StrictMode -Version "Latest"
Log-Start
....
The question is how can I make the Log_Start function to use variables I assigned in the beginning of the script, or it is not possible with declaration of [CmdletBinding()] and function itself.
If I try to run it the way it is coded it is prompting me to enter the path and logname, I thought it should have used what I already defined. Apparently I am missing the concept. I surely can just assign the values I need right in the param declaration for the function, but I am planning to use couple of more functions like log-write and log-finish,and would not want to duplicate the same values.
What am I missing?

You defined your custom parameters at the top of your script and now you must pass them them to the function by changing
Log-Start
line to read
Log-Start $LogPath $LogName
Though you would be better off naming your parameters differently to avoid confussion. You don't really need CmdletBinding() declaration unless you plan to utilise common parameters like -Verbose or -Debug with your function so you could get rid of the following 2 lines:
[CmdletBinding()]
Param ([Parameter(Mandatory=$true)][string]$LogPath, [Parameter(Mandatory=$true)][string]$LogName)
and your script would still work.

If you want to include settings from a config file, one approach is hashtables. Your config file would look like this:
logpath=c:\somepath\
server=server.domain
Then your script would have an extra var pointing to a config file and a function to import it:
$configFile = "c:\some.config";
function GetConfig(){
$tempConfig = #{};
$configLines = cat $configFile -ErrorAction Stop;
foreach($line in $configLines){
$lineArray = $line -split "=";
$tempConfig.Add($lineArray[0].Trim(), $lineArray[1].Trim());
}
return $tempConfig;
}
$config = GetConfig
You can then assign config values to variables:
$LogPath = $conifg.Item("logpath")
$server = $conifg.Item("server")
Or use them access directly
$conifg.Item("server")

Related

New-Item messing up my variable PowerShell [duplicate]

This question already has an answer here:
Powershell Join-Path showing 2 dirs in result instead of 1 - accidental script/function output
(1 answer)
Closed 1 year ago.
I wrote a very simple script to acquire a random free drive letter.
The function finds a random free letter , creates a new empty text file of that drive letter name eg. Q.txt
I then return the value as $new_letter but when it comes out of the function somehow newly file created path is a part of the variable C:\AppPack\Logs\Q.txt Q
Is it something New-Item messing up with my $new_letter variable ?
function get_drive_letter()
{
$letter_acquired = $false
Do
{
$new_letter = Get-ChildItem function:[h-z]: -Name | ForEach-Object { if (!(Test-Path $_)){$_} } | random -Count 1 | ForEach-Object {$_ -replace ':$', ''}
write-host ("RIGHT AFTER " + $new_letter)
if (!(test-path "C:\AppPack\Logs\$new_letter.txt"))
{
New-Item -Path C:\AppPack\Logs\ -Name "$new_letter.txt" -ItemType "file"
write-host ("FROM FUNCTION " + $new_letter)
$letter_acquired = $true
return $new_letter
}
else
{
write-host ("LETTER USED ALREADY")
write-host ($new_letter)
}
}
while($letter_acquired = $false)
}
$drive_letter = $null
$drive_letter = get_drive_letter
write-host ("RIGHT AFTER FUNCTION " + $drive_letter)
OUTPUT :
RIGHT AFTER Q
FROM FUNCTION Q
RIGHT AFTER FUNCTION C:\AppPack\Logs\Q.txt Q
A PowerShell function outputs everything, not just the result of the expression right after return!
The additional file path you see is the output from New-Item ... - it returns a FileInfo object for the file you just created.
You can suppress output by assigning it to the special $null variable:
# Output from New-Item will no longer "bubble up" to the caller
$null = New-Item -Path C:\AppPack\Logs\ -Name "$new_letter.txt" -ItemType "file"
return $new_letter
Or by piping to Out-Null:
New-Item ... |Out-Null
Or by casting the entire pipeline to [void]:
[void](New-Item ...)
Although I recommend explicitly handling unwanted output at the call site, you can also work around this behavior with a hoisting trick.
To demonstrate, consider this dummy function - let's say we "inherit" it from a colleague who didn't always write the most intuitive code:
function Get-RandomSquare {
"unwanted noise"
$randomValue = 1..100 |Get-Random
"more noise"
$square = $randomValue * $randomValue
return $square
}
The function above will output 3 objects - the two garbage strings one-by-one, followed by the result that we're actually interested in:
PS ~> $result = Get-RandomSquare
PS ~> $result
unwanted noise
more noise
6400
Let's say we've been told to make as few modifications as possible, but we really need to suppress the garbage output.
To do so, nest the entire function body in a new scriptblock literal, and then invoke the whole block using the dot-source operator (.) - this forces PowerShell to execute it in the function's local scope, meaning any variable assignments persist:
function Get-RandomSquare {
# suppress all pipeline output
$null = . {
"unwanted noise"
$randomValue = 1..100 |Get-Random
"more noise"
$square = $randomValue
return $square
}
# variables assigned in the block are still available
return $square
}

Powershell not returning correct value

As some background, this should take an excel file, and convert it to PDF (and place the PDF into a temporary folder).
E.g. 'C:\Users\gjacobs\Desktop\test\stock.xlsx'
becomes
'C:\Users\gjacobs\Desktop\test\pdf_merge_tmp\stock.pdf'
However, the new file path does not return correctly.
If I echo the string $export_name from within the function, I can see that it has the correct value: "C:\Users\gjacobs\Desktop\test\pdf_merge_tmp\stock.pdf".
But once $export_name is returned, it has a different (incorrect value): "C:\Users\gjacobs\Desktop\test\pdf_merge_tmp C:\Users\gjacobs\Desktop\test\pdf_merge_tmp\stock.pdf".
function excel_topdf{
param(
$file
)
#Get the parent path
$parent = Split-Path -Path $file
#Get the filename (no ext)
$leaf = (Get-Item $file).Basename
#Add them together.
$export_name = $parent + "\pdf_merge_tmp\" + $leaf + ".pdf"
echo ($export_name) #prints without issue.
#Create tmp dir
New-Item -Path $parent -Name "pdf_merge_tmp" -ItemType "Directory" -Force
$objExcel = New-Object -ComObject excel.application
$objExcel.visible = $false
$workbook = $objExcel.workbooks.open($file, 3)
$workbook.Saved = $true
$xlFixedFormat = “Microsoft.Office.Interop.Excel.xlFixedFormatType” -as [type]
$workbook.ExportAsFixedFormat($xlFixedFormat::xlTypePDF, $export_name)
$objExcel.Workbooks.close()
$objExcel.Quit()
return $export_name
}
$a = excel_topdf -file 'C:\Users\gjacobs\Desktop\test\stock.xlsx'
echo ($a)
The issue you're experiencing is caused by the way how PowerShell returns from functions. It's not something limited to New-Item cmdlet. Every cmdlet which returns anything would cause function output being altered with the value from that cmdlet.
As an example, let's take function with one cmdlet, which returns an object:
function a {
Get-Item -Path .
}
$outputA = a
$outputA
#### RESULT ####
Directory:
Mode LastWriteTime Length Name
---- ------------- ------ ----
d--hs- 12/01/2021 10:47 C:\
If you want to avoid that, these are most popular options (as pointed out by Lasse V. Karlsen in comments):
# Assignment to $null (or any other variable)
$null = Get-Item -Path .
# Piping to Out-Null
Get-Item -Path . | Out-Null
NOTE: The behavior described above doesn't apply to Write-Host:
function b {
Write-Host "bbbbbb"
}
$outputB = b
$outputB
# Nothing displayed
Interesting thread to check if you want to learn more.

Powershell Copy Directories - Using A CSV Listing - Source - Destination Path

I am trying to Copy Directories - folders and Sub folders to another location using a CSV file that lists the source and destination of each directory or folder to be copied.
The Contents of the CSV are as such below:
I have referenced this thread:
https://serverfault.com/questions/399325/copying-list-of-files-through-powershell
Import-CSV C:\Users\WP\Desktop\a.csv | foreach{Copy-item "$_.Source" "$_.Destination"}
Error Received
CategoryInfo : ObjectNotFound: (#{Source=C:String) [Copy-Item], DriveNotFoundException
+ FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.CopyItemCommand
The other question I have is if in the CSV I want to copy to a folder that does not exists in the destination - can I use the CSV to command powershell to create the folder?
Thank you for your advice.
PowerShell will not expand the variable and access the property of the object inside the variable if you have them placed in double quotes by default. Only the '$_' is being expanded and '.source' is being tacked on to the end of the string, so from the view of the shell, your command looks something like Copy-item "{source=C:\Users\WP\Desktop\a;Destination=C:\Users\WP\Desktop\a}.Source" "{source=C:\Users\WP\Desktop\a;Destination=C:\Users\WP\Desktop\a}.Destination", which is probably not what you mean.
Here is the syntax that should work (I also included -Recurse so that it will copy the items inside the directory as well)
Import-CSV C:\Users\WP\Desktop\a.csv | foreach{Copy-item -Path $_.Source -Destination $_.Destination -Recurse}
Note: if you want to access the properties on an object inside of double quotes, use this syntax "$($_.source)".
For a csv like this:
Source,Destination
D:\junk\test1,D:\junk\test3
D:\junk\test2,D:\junk\test4
You can use code like the following:
$csv = Import-Csv D:\junk\test.csv
$csv | ForEach-Object {
if (-not (Test-Path $_.Destination)) {
New-Item -Name $_.Destination -ItemType Directory -Force -WhatIf
}
Copy-Item $_.Source $_.Destination -Recurse -Force -WhatIf
}
Suggestions for learning more about PowerShell:
Use WhatIf to test things.
Research what each line of this code does.
Experiment with code to see what it does.
Learn and use the debugger (PowerShell ISE) to help you write better code.
Remove the WhatIf parameters from the code to have it execute for real...
If you have dozens of problems that all involve doing the same thing with each element of a list, you might want to consider getting or writing a generic CSV template expander tool, like Expand-csv. With this tool you start with a CSV file and a template, and generate a script that contains all the commands.
Sample.csv looks like this:
Source,Destination
C:\Users\WP\Desktop\a,C:\Users\WP\Desktop\c
C:\Users\WP\Desktop\b,C:\Users\WP\Desktop\d
Sample.tmplt looks like this:
Copy-Item -Path $Source -Destination $Destination -Recurse
And the command to invoke Expand-csv looks like this:
Expand-csv Sample.csv Sample.tmplt > Sample.ps1
The output file, Sample.ps1 contains one copy command for each entry in the CSV file
And here is the definition of Expand-csv:
<# This function is a table driven template tool.
It's a refinement of an earlier attempt.
It generates output from a template and
a driver table. The template file contains plain
text and embedded variables. The driver table
(in a csv file) has one column for each variable,
and one row for each expansion to be generated.
5/13/2015
#>
function Expand-csv {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string] $driver,
[Parameter(Mandatory=$true)]
[string] $template
)
Process
{
$OFS = "`r`n"
$list = Import-Csv $driver
[string]$pattern = Get-Content $template
foreach ($item in $list) {
foreach ($key in $item.psobject.properties) {
Set-variable -name $key.name -value $key.value
}
$ExecutionContext.InvokeCommand.ExpandString($pattern)
}
}
}

Powershell WebClient DownloadFile Exception Illegal Characters in Path

I am trying to download zip files from an FTP site, based off retrieving a directory list to find file names.
Download Portion:
$folderPath='ftp://11.111.11.11/'
$target = "C:\Scripts\ps\ftpdl\"
Foreach ($file in ($array | where {$_ -like "data.zip"})) {
$Source = $folderPath+$file
$Path = $target+$file
#$Source = "ftp://11.111.11.11/data.zip"
#$Path = "C:\Scripts\ps\ftpdl\data.zip"
$source
Write-Verbose -Message $Source -verbose
$path
Write-Verbose -message $Path -verbose
$U = "User"
$P = "Pass"
$WebClient2 = New-Object System.Net.WebClient
$WebClient2.Credentials = New-Object System.Net.Networkcredential($U, $P)
$WebClient2.DownloadFile( $source, $path )
}
If I use the commented out and define the string it downloads correctly. But if I run it as shown I receive the exception error illegal characters in path. Interestingly enough, there is a difference between write-verbose and not.
Output when run as shown:
ftp://11.111.11.11/data.zip
data.zip
C:\Scripts\ps\ftpdl\data.zip
data.zip
Exception calling "DownloadFile" with "2" .........
Output when run with hard coded path & source
ftp://11.111.11.11/data.zip
VERBOSE: ftp://11.111.11.11/data.zip
C:\Scripts\ps\ftpdl\data.zip
VERBOSE: C:\Scripts\ps\ftpdl\data.zip
And the file downloads nicely.
Well, of course once I post the question I figured it out. My $array contained `n and `r characters. I needed to find and replace both of them out.
$array=$array -replace "`n",""
$array=$array -replace "`r",""

Passing an array of URLs as an argument to Powershell

I am trying to write a script that will take a text file containing URL links to documents and download them. I am having a hard time understanding how to pass the arguments and manipulate them in powershell. Here is what I got so far. I think I should be using the param method of taking an argument so I can require it for the script, but $args seemed easier on face value... A little help would be much appreciated.
**UPDATE
$script = ($MyInvocation.MyCommand.Name)
$scriptName = ($MyInvocation.MyCommand.Name -replace "(.ps1)" , "")
$scriptPath = ($MyInvocation.MyCommand.Definition)
$scriptDirectory = ($scriptPath.Replace("$script" , ""))
## ##################################
## begin code for directory creation.
## ##################################
## creates a direcory based on the name of the script.
do {
$scriptFolderTestPath = Test-Path $scriptDirectory\$scriptName -PathType container
$scriptDocumentFolderTestPath = Test-Path $scriptFolder\$scriptName"_Script_Documents" -PathType container
$scriptLogFolderTestPath = Test-Path $scriptFolder\$scriptName"_Script_Logs" -PathType container
if ($scriptFolderTestPath -match "False") {
$scriptFolder = New-Item $scriptDirectory\$scriptName -ItemType directory
}
elseif ($scriptDocumentFolderTestPath -match "False") {
New-Item $scriptFolder\$scriptName"_Script_Documents" -ItemType directory
}
elseif ($scriptLogFolderTestPath -match "False") {
New-Item $scriptFolder\$scriptName"_Script_Logs" -ItemType directory
}
} Until (($scriptFolderTestPath -match "True") -and ($scriptDocumentFolderTestPath -match "True") -and ($scriptLogFolderTestPath -match "True"))
## variables for downloading and renaming code.
$date = (Get-Date -Format yyyy-MM-dd)
## ################################
## begin code for link downloading.
## ################################
## gets contents of the arguement variable.
Get-Content $linkList
## downloads the linked file.
Invoke-WebRequest $linkList
Resulting Errors
PS C:\Windows\system32> C:\Users\Steve\Desktop\Website_Download.ps1
cmdlet Website_Download.ps1 at command pipeline position 1
Supply values for the following parameters:
linkList: C:\Users\Steve\Desktop\linkList.txt
Directory: C:\Users\Steve\Desktop\Website_Download
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 10/27/2012 3:59 PM Website_Download_Script_Documents
d---- 10/27/2012 3:59 PM Website_Download_Script_Logs
Get-Content : Cannot find path 'C:\Users\Steve\Desktop\linkList.txt' because it does not exist.
At C:\Users\Steve\Desktop\Website_Download.ps1:42 char:1
+ Get-Content $linkList
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Users\Steve\Desktop\linkList.txt:String) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Invoke-WebRequest : Could not find file 'C:\Users\Steve\Desktop\linkList.txt'.
At C:\Users\Steve\Desktop\Website_Download.ps1:45 char:1
+ Invoke-WebRequest $linkList
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.FileWebRequest:FileWebRequest) [Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
There is no difference in passing an array of arguments in Powershell, compared to any other type of arguments. See here for how it's done. Considering you have a text file, you don't need to pass an array of arguments, and only need to pass a file name, so just a string.
I don't have any experience with Powershell 3.0, which is what you are using (judging by the presence of Invoke-WebRequest in your code), but I would start with something like this:
$URLFile = #"
http://www.google.ca
http://www.google.com
http://www.google.co.uk/
"#
$URLs = $URLFile -split "`n";
$savedPages = #();
foreach ($url in $URLs) {
$savedPages += Invoke-WebRequest $url
}
That is, you have a single file, all in one place, and make sure you receive your content correctly. Not sure why you would need Start-BitsTransfer, since Invoke-WebRequest will already get you page contents. Note that I did not do anything with $savedPages, so my code is effectively useless.
After that, contents of $URLFile goes into a file and you replace a call to it with
gc "Path_To_Your_File"`
If still working, introduce a $Path parameter to your script like this:
param([string]$Path)
test again, and so on. If you are new to Powershell, always start with smaller code pieces and keep growing to include all functionality you need. If you start with a big piece, chances are you will never finish.
Figured this out with the link from Neolisk about handling params. Then changed some code at the end to create another variable and handle things as I normally would. Just some confusion with passing params.
## parameter passed to the script.
param (
[parameter(Position=0 , Mandatory=$true)]
[string]$linkList
)
## variables for dynamic naming.
$script = ($MyInvocation.MyCommand.Name)
$scriptName = ($MyInvocation.MyCommand.Name -replace "(.ps1)" , "")
$scriptPath = ($MyInvocation.MyCommand.Definition)
$scriptDirectory = ($scriptPath.Replace("$script" , ""))
## ##################################
## begin code for directory creation.
## ##################################
## creates a direcory based on the name of the script.
do {
$scriptFolderTestPath = Test-Path $scriptDirectory\$scriptName -PathType container
$scriptDocumentFolderTestPath = Test-Path $scriptFolder\$scriptName"_Script_Documents" -PathType container
$scriptLogFolderTestPath = Test-Path $scriptFolder\$scriptName"_Script_Logs" -PathType container
if ($scriptFolderTestPath -match "False") {
$scriptFolder = New-Item $scriptDirectory\$scriptName -ItemType directory
}
elseif ($scriptDocumentFolderTestPath -match "False") {
New-Item $scriptFolder\$scriptName"_Script_Documents" -ItemType directory
}
elseif ($scriptLogFolderTestPath -match "False") {
New-Item $scriptFolder\$scriptName"_Script_Logs" -ItemType directory
}
} Until (($scriptFolderTestPath -match "True") -and ($scriptDocumentFolderTestPath -match "True") -and ($scriptLogFolderTestPath -match "True"))
## variables for downloading and renaming code.
$date = (Get-Date -Format yyyy-MM-dd)
## ################################
## begin code for link downloading.
## ################################
## gets contents of the arguement variable.
$webTargets = Get-Content $linkList
## downloads the linked file.
Invoke-WebRequest $webTargets