Passing an array of URLs as an argument to Powershell - parameter-passing

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

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.

Use ping output to create a CSV

I have written following code to use ping command to ping multiple computers, I would like to capture following values it:
Reply from / Request timed out etc.
Actual IP Address of the remote host
This function is working fine however, if I am trying to get its returned values to a CSV I am unable to do so.
# Declaration of the function name and expected parameters
Function Ping-Check{
[cmdletbinding()]
PARAM (
[Parameter(Mandatory=$True,
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True,
HelpMessage='Enter the name of the remote host.')]
[String]$ObjCompName
)
Begin{
# Setup the Process startup info
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "ping.exe"
$pinfo.Arguments = " -a " + $ObjCompName + " -n 2"
$pinfo.UseShellExecute = $false
$pinfo.CreateNoWindow = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.RedirectStandardError = $true
}
Process{
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
# Redirect the Output
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
}
End{
Write-Output "stdout: $stdout"
Write-Output "stderr: $stderr"
Write-Output "exit code: " + $p.ExitCode
}
}
There is a Test-Connection cmdlet available in powershell which returns an object array with properties you can use. If you wan't to stick with your attempt, be aware that you have to parse the output yourself.

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",""

Windows PowerShell invoking function with parameters

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")