I am trying to create a PowerShell script that uses the below code. I believe that the projects folder having spaces is causing my error but I am unable to come up with a solution. I have tried numerous things from different stackoverflow solutions with no success. I am not a PowerShell guy so I know it is something simple (maybe). Anyone help me out there...
Script code:
Write-Host ""
$projectsFolder = Read-Host **'E:\Work - VS.NET 2022'**
Write-Host ""
Write-Host "Adding Path Exclusion: " $projectsFolder
Add-MpPreference -ExclusionPath $projectsFolder
foreach ($exclusion in $pathExclusions)
{
Write-Host "Adding Path Exclusion: " $exclusion
Add-MpPreference -ExclusionPath $exclusion
}
Error:
Adding Path Exclusion: Add-MpPreference : Cannot validate argument
on parameter 'ExclusionPath'. The argument is null or empty. Provide
an argument that is not null or empty, and then try the command
again. At E:\Work - VS.NET 2022\Tools\Visual
Studio\Windows_Defender_Exclusions_VS_2022.ps1:54 char:33
+ Add-MpPreference -ExclusionPath $projectsFolder
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Add-MpPreference], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Add-MpPreference
I do not think you want to read host. I think you want to get the content of a file. Your code will be like the bottom but I also added a simple read-host for you to review.
==========read-host================
$YesNoResponse = $null
while ($YesNoResponse -ne "y" -and $YesNoResponse -ne "n") {
Clear-Host
write-host “Yes or No?”
Write-Host "Please confirm."
$YesNoResponse = Read-Host " [Y/N] "
If ($YesNoResponse -eq "") {
Write-Host "Enter was inputted"
Return $null
}
}
I think you are trying to do something like this. If you need something else please expand.
Write-Host ""
$projectsFolder = Import-Csv 'E:\Work - VS.NET 2022'
Write-Host ""
Write-Host "Adding Path Exclusion: "
$projectsFolder = Add-MpPreference -ExclusionPath $projectsFolder
foreach ($exclusion in $pathExclusions) {
Write-Host "Adding Path Exclusion: "
$exclusion = Add-MpPreference -ExclusionPath $exclusion }
The problem in your script is that the array you use for your loop ($pathExclusions) is not created and thus is empty, resulting in $exclusion variable being always empty/null... which explain your error message.
If the goal is to prompt the user to enter manually a list of directories to be processed, then you can do something like that:
# Create the exclusions array (of strings)
$pathExclusions = #()
Write-Host ""
$projectsFolder = Read-Host **'E:\Work - VS.NET 2022'**
# Press Enter at read-host prompt to exit the loop
while ($projectsFolder -ne "") {
$pathExclusions += $projectsFolder
$projectsFolder = Read-Host **'E:\Work - VS.NET 2022'**
}
Write-Host ""
# Process each item in the exclusions array
# Loop not executed if $pathExclusions is empty so no error message
foreach ($exclusion in $pathExclusions) {
Write-Host "Adding Path Exclusion: " $exclusion
Add-MpPreference -ExclusionPath $exclusion
}
If the goal is to get the list of directories from a file, then just populate the exclusions array by getting the file content instead of the Read-Host loop:
$pathExclusions = Get-Content -Path "c:\temp\exclusionsfile.txt"
Related
I have this PowerShell script that I'm working on. CSV file is imported to get source and destination paths. The goal is to move files from a SFTP/FTP server into a destination and send an email report.
Task scheduler will run this code every hour. And if there's a new file, as email will be sent out.
It's almost done, but two things are missing:
Check if the file already exists and Body email seems empty: Getting the following error: Cannot validate argument on parameter 'Body'. The argument is null or empty. Provide an argument that is not
null or empty, and then try the command again.
I would like some assistance on how to check if the file exists and how to get this email if a new file was dropped and copied to the destination list
$SMTPBody = ""
$SMTPMessage = #{
"SMTPServer" = ""
"From" = ""
"To" = ""
"Subject" = "New File"
}
try {
# Load WinSCP .NET assembly
Add-Type -Path "C:\Program Files (x86)\WinSCP\WinSCPnet.dll"
# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property #{
Protocol = [WinSCP.Protocol]::sftp
HostName = ""
UserName = ""
Password = ""
PortNumber = "22"
FTPMode = ""
GiveUpSecurityAndAcceptAnySshHostKey = $true
}
$session = New-Object WinSCP.Session
try
{
# Connect
$session.Open($sessionOptions)
# Download files
$transferOptions = New-Object WinSCP.TransferOptions
$transferOptions.TransferMode = [WinSCP.TransferMode]::Binary
Import-Csv -Path "D:\FILESOURCE.csv" -ErrorAction Stop | foreach {
$synchronizationResult = $session.SynchronizeDirectories(
[WinSCP.SynchronizationMode]::Local, $_.Destination, $_.Source, $False)
$synchronizationResult.Check()
foreach ($download in $synchronizationResult.Downloads ) {
Write-Host "File $($download.FileName) downloaded" -ForegroundColor Green
$SMTPBody +=
"`n Files: $($download.FileName -join ', ') `n" +
"Current Location: $($_.Destination)`n"
Send-MailMessage #SMTPMessage -Body $SMTPBody
}
$transferResult =
$session.GetFiles($_.Source, $_.Destination, $False, $transferOptions)
#Find the latest downloaded file
$latestTransfer =
$transferResult.Transfers |
Sort-Object -Property #{ Expression = { (Get-Item $_.Destination).LastWriteTime }
} -Descending |Select-Object -First 1
}
if ($latestTransfer -eq $Null) {
Write-Host "No files found."
$SMTPBody += "There are no new files at the moment"
}
else
{
$lastTimestamp = (Get-Item $latestTransfer.Destination).LastWriteTime
Write-Host (
"Downloaded $($transferResult.Transfers.Count) files, " +
"latest being $($latestTransfer.FileName) with timestamp $lastTimestamp.")
$SMTPBody += "file : $($latestTransfer)"
}
Write-Host "Waiting..."
Start-Sleep -Seconds 5
}
finally
{
Send-MailMessage #SMTPMessage -Body $SMTPBody
# Disconnect, clean up
$session.Dispose()
}
}
catch
{
Write-Host "Error: $($_.Exception.Message)"
}
I believe your code has more problems than you think.
Your combination of SynchronizeDirectories and GetFiles is suspicious. You first download only the new files by SynchronizeDirectories and then you download all files by GetFiles. I do not think you want that.
On any error the .Check call will throw and you will not collect the error into your report.
You keep sending partial reports by Send-MailMessage in the foreach loop
This is my take on your problem, hoping I've understood correctly what you want to implement:
$SMTPBody = ""
Import-Csv -Path "FILESOURCE.csv" -ErrorAction Stop | foreach {
Write-Host "$($_.Source) => $($_.Destination)"
$SMTPBody += "$($_.Source) => $($_.Destination)`n"
$synchronizationResult =
$session.SynchronizeDirectories(
[WinSCP.SynchronizationMode]::Local, $_.Destination, $_.Source, $False)
$downloaded = #()
$failed = #()
$latestName = $Null
$latest = $Null
foreach ($download in $synchronizationResult.Downloads)
{
if ($download.Error -eq $Null)
{
Write-Host "File $($download.FileName) downloaded" -ForegroundColor Green
$downloaded += $download.FileName
$ts = (Get-Item $download.Destination).LastWriteTime
if ($ts -gt $latest)
{
$latestName = $download.FileName;
$latest = $ts
}
}
else
{
Write-Host "File $($download.FileName) download failed" -ForegroundColor Red
$failed += $download.FileName
}
}
if ($downloaded.Count -eq 0)
{
$SMTPBody += "No new files were downloaded`n"
}
else
{
$SMTPBody +=
"Downloaded $($downloaded.Count) files:`n" +
($downloaded -join ", ") + "`n" +
"latest being $($latestName) with timestamp $latest.`n"
}
if ($failed.Count -gt 0)
{
$SMTPBody +=
"Failed to download $($failed.Count) files:`n" +
($failed -join ", ") + "`n"
}
$SMTPBody += "`n"
}
It will give you a report like:
/source1 => C:\dest1`
Downloaded 3 files:
/source1/aaa.txt, /source1/bbb.txt, /source1/ccc.txt
latest being /source1/ccc.txt with timestamp 01/29/2020 07:49:07.
/source2 => C:\dest2
Downloaded 1 files:
/source2/aaa.txt
latest being /source2/aaa.txt with timestamp 01/29/2020 07:22:37.
Failed to download 1 files:
/source2/bbb.txt
To check and make sure the csv file exists before you process the entire thing, you can use the Test-Path,
...
if (!(Test-Path D:\FileSource.csv)) {
Write-Output "No File Found"
$SMTPBody += "There are no new files at the moment"
return; # Dont run. file not found. Exit out.
}
Import-Csv -Path "D:\FILESOURCE.csv" -ErrorAction Stop | foreach {
...
and for the Body error you are getting, it is coming from the finally loop because there are cases where $SMTPBody would be null. This will no longer be an issue because $SMTPBody will have some text when file is not found at the beginning.
Even though you are using return in the if statement to check if the file exists, finally will always get executed. Since we updated $smtpbody, your Send-MailMessage will no longer error out.
Update
If you want to check if the file you are downloading already exists, you can use the if statement like this,
foreach ($download in $synchronizationResult.Downloads ) {
if (!(Test-Path Join-Path D: $download.FileName) {
$SMTPBody += "File $($download.Filename) already exists, skipping."
continue # will go to the next download...
}
Write-Host "File $($download.FileName) downloaded" -ForegroundColor Green
...
If you do get the error regarding body, thats mostly because your script came across an exception and was sent straight over to finally statement. Finally statement sends the email with empty body because it was never set (due to exception). I would recommend using the debugger (step through) and see which step causes the exception and look into adding steps to make sure script doesnt fail.
I understand Stackoverflow isn't a write your code for you forum, absolutely, but I'm finding it very difficult to find a good example of try/catch proper usage in Powershell. I have read up on fundamentals, and understand the theoretical concept, but execution I'm struggling with.
Here is a simple script that queries Active Directory:
do {
clear
Import-Module active*
"============ WhoIs Lookup ============="
Write-Host ""
$who = Read-Host "WhoIs";
$req = Get-ADUser -Identity $who
Write-Host ''
Write-Host "$who is " -NoNewline
Write-Host $req.Name -ForegroundColor Cyan
pause
} while ($run =1)
An example error is:
Get-ADUser : Cannot find an object with
identity: '5621521' under: 'DC=dcsg,DC=com'.
At C:\Tools\CSOCTools\Who_Is\whoIs.ps1:10
char:12
+ $req = Get-ADUser -Identity $who
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFoun
d: (5621521:ADUser) [Get-ADUser], ADIde
ntityNotFoundException
+ FullyQualifiedErrorId : ActiveDirecto
ryCmdlet:Microsoft.ActiveDirectory.Mana
gement.ADIdentityNotFoundException,Micr
osoft.ActiveDirectory.Management.Comman
ds.GetADUser
How would I catch this User Not Found error?
Simple example:
try {
Get-ADUser -Identity “bleh”
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
{
Write-Warning “AD computer object not found”
}
For your case:
do {
clear
Import-Module active*
"============ WhoIs Lookup ============="
Write-Host ""
$who = Read-Host "WhoIs";
try {
$req = Get-ADUser -Identity $who
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
{
Write-Warning “AD user object not found”
Write-Host ''
Write-Host "$who is " -NoNewline
Write-Host $req.Name -ForegroundColor Cyan
}
pause
} while ($run =1)
Edit: I put the Write-Host into the catch as you're eventually trying to reference to NULL when there's no object.
I got a very good example from here. For the exception types after the Catch (where I have two of them) I just grabbed them straight from the error message you provided. I haven't tried this out many times in my experience, let me know if it works for ya!
Try
{
do {
clear
Import-Module active*
"============ WhoIs Lookup ============="
Write-Host ""
$who = Read-Host "WhoIs";
$req = Get-ADUser -Identity $who
Write-Host ''
Write-Host "$who is " -NoNewline
Write-Host $req.Name -ForegroundColor Cyan
pause
} while ($run =1)
}
Catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException],[Microsoft.ActiveDirectory.Management.Commands.GetADUser]
{
# Error message here
}
I'm just a typical admin trying to make a simple script for some IT assistants in remote offices, to make domain joins easier while minimizing potential errors. The script's end game is to run the one-liner command Add-Computer -DomainName $DomainToJoin -OUPath $LocDN -NewName $WS_NewName -Restart.
But the whole script is supposed to include input validation for the computer's new name as well as for the target OU for the two-letter code for the remote office.
Googling for code snippets for days, esp. from sites like yours, was very helpful. But the problem I have now is I couldn't find the right codes to combine Read-Host , input length validation, and TRAP to work together without losing the value for my variables.
Pardon my coding as obviously I'm no real PS scripter, and I know the wrong portions of it are very basic. I would want to spend more time if I had the luxury, but I would really so much appreciate it if you could point me in the right direction.
Thank you so much in advance.
Please see my code below:
# START: Display name and purpose of invoked script
$path = $MyInvocation.MyCommand.Definition
Clear-Host
Write-Host $path
Write-Host " "
Write-Host "This script will allow you to do the following in a single-step process:"
Write-Host "(1) RENAME this computer"
Write-Host "(2) JOIN it to MYDOMAIN"
Write-Host "(3) MOVE it to a target OU"
Write-Host "(4) REBOOT"
Write-Host " "
Pause
# Function: PAUSE
Function Pause ($Message = "Press any key to continue . . . ") {
if ((Test-Path variable:psISE) -and $psISE) {
$Shell = New-Object -ComObject "WScript.Shell"
$Button = $Shell.Popup("Click OK to continue.", 0, "Script Paused", 0)
}
else {
Write-Host -NoNewline $Message
[void][System.Console]::ReadKey($true)
Write-Host
}
Write-Host " "
}
# Function: Define the parameters
Function Define-Parameters {
# Specify new computer name, with validation and TRAP
$WS_NewName = $null
while ($null -eq $WS_NewName) {
[ValidateLength(8,15)]$WS_NewName = [string](Read-Host -Prompt "NEW NAME of computer (8-15 chars.)" )
TRAP {"" ;continue}
}
Write-Host " "
# Domain to join.
$DomainToJoin = 'mydomain.net'
# Specify the target OU, with validation and trap
$baseOU='OU=Offices OU,DC=mydomain,DC=net'
$OU2 = $null
while ($null -eq $OU2) {
[ValidateLength(2,2)]$OU2 = [string](Read-Host -Prompt 'Target OU (TWO-LETTER code for your office)' )
TRAP {"" ;continue}
}
Write-Host " "
$LocDN = "OU=$OU2,$baseOU"
}
# Function: Summary and confirmation screen for defined parameters.
Function Confirm-Parameters {
Write-Host "==========================================================================="
Write-Host "Please confirm that you are joining this computer to
$DomainToJoin (MYDOMAIN)"
Write-Host "with the following parameters:"
Write-Host ""
Write-Host ""
Write-Host "Computer's NEW NAME: $WS_NewName"
# Write-Host "Domain to Join: $DomainToJoin"
Write-Host "TARGET mission OU: $OU2"
}
# Call Define-Parameters Function
Define-Parameters
# Call Confirm-Parameters Function
Confirm-Parameters
<#
Some more code here
#>
# FINAL COMMAND if all else works: Join the computer to the domain, rename it, and restart it.
# Add-Computer -DomainName $DomainToJoin -OUPath $LocDN -NewName $WS_NewName -Restart
In your code, you have a lot of things defined very strangely. Your functions create a new scope and the variables you're trying to define therein will disappear after calling them unless you change the variable scope (in this case, to $script: or $global:). Also, to use functions, you need to define them first (your Pause doesn't actually do anything)
Here's something you can do with your Define-Parameters function (I suggest looking at Get-Verb)
# Domain to join.
$DomainToJoin = 'mydomain.net'
# Function: Define the parameters
Function Get-Parameters {
do {
$global:WS_NewName = Read-Host -Prompt 'NEW NAME of computer (8-15 chars)'
} until ($WS_NewName.Length -gt 7 -and $WS_NewName.Length -lt 16)
''
do {
$global:OU2 = Read-Host -Prompt 'Target OU (TWO-LETTER code for your office)'
} until ($OU2 -match '^[a-z]{2}$')
''
$OU2 = "OU=$global:OU2,OU=Offices OU,DC=mydomain,DC=net"
}
I'd strongly recommend moving away from the ISE to do your testing and test in an actual powershell console.
Perhaps Try/Catch block instead of trap?
Try {
[ValidatePattern('^\w{8,15}$')]$compname=read-host 'enter comp name' -ErrorAction Stop
[ValidatePattern('^\w{2}$')]$OU=read-host 'enter OU name' -ErrorAction Stop
}
Catch {
$ErrorMessage = $_.Exception.Message
$ErrorLineNumber = $_.InvocationInfo.ScriptLineNumber
$ErrorCommandName = $_.InvocationInfo.InvocationName
Write-Error -Message "The error message was: <$ErrorMessage>, script line: <$ErrorLineNumber>, command name: <$ErrorCommandName>"
exit 255
}
I have written the following script:
clear-host
## Step 1 (Creating 2 command line variables)
##########################################################################################################################
Clear-Host
$WorkingDirectory = $ARGS[0]
$DirectoryName = $ARGS[1]
##Step 2 (Check if variables are empty and get user input if necessary)
##########################################################################################################################
if ("$WorkingDirectory" -eq "")
{
Write-Warning "Parameter Required"
$WorkingDirectory = Read-Host "Enter the absolute path to working directory "
}
if ("$DirectoryName" -eq "")
{
Write-Warning "Paramater Required"
$DirectoryName = Read-Host "Enter a directory name to search for in $WorkingDirectory "
}
for ($i=0; $i -le 2; $i++) {write-host ""} # to print 2 blank lines using for loop
##Step 3 (Test to see if PWD is equal to $WorkingDirectory and if not move the location )
##########################################################################################################################
if ("$PWD" -ne "$WorkinDirectory")
{
write-host "You are not in the < $WorkingDirectory > . Do you wish to move? Press CTRL+C to exit or wait"
write-host ""
pause
Set-Location -Path $WorkingDirectory
}
##Step 4 (Test to see the directory exists in the working directory or not )
##########################################################################################################################
if((Test-Path $DirectoryName) -ne "True")
{
write-host "Directory $DirectoryName does not exist"
}
The problem I am facing at step 3. It should check my present location with the $WorkingDirectory variable, if they are not same then the location will be changed. But the problem is even if I am at the same location as the $WorkingDirectory it is still giving me the warning message. how to solve that
I have created the following Powershell script which I hope to use to copy files to a network share.
function Copy-Deploy
{
param(
[Parameter(Position=0,Mandatory=$true,HelpMessage="Path to scripts")]
[Alias("pth")]
[string]$ScriptPath,
[Parameter(Position=1,Mandatory=$true,HelpMessage="Deployment script filename")]
[Alias("dep")]
[string]$PowershellDeploymentScript,
[Parameter(Position=2,Mandatory=$true,HelpMessage="MSI filename")]
[Alias("m")]
[string]$MSI,
[Parameter(Position=3,Mandatory=$true,HelpMessage="Filename of the MSBuild script (.btdfproj)")]
[Alias("msb")]
[string]$MSBuildScript,
[Parameter(Position=4,HelpMessage="UNC path to target server folder")]
[Alias("server")]
[string]$TargetServerPath
)
$ErrorActionPreference="Stop"
#Step 1 : copy the MSI, .btdfproj script and control powershell script to the remote server
Write-Host " Going to enter the mis script block"
$misScript =
{
Write-Host " Path+Filename = {0}({1})" -f $ScriptPath, $PowershellDeploymentScript
Write-Host " Going to copy files"
Write-Host " ScriptPath = $ScriptPath"
Write-Host " PowershellDeploymentScript = $PowershellDeploymentScript"
Write-Host " MSI = $MSI"
Write-Host " MSBuildScript = $MSBuildScript"
Write-Host " TargetServerPath = $TargetServerPath"
Copy-Item -Path "$ScriptPath" + "$PowershellDeploymentScript" -Destination "$TargetServerPath"
Copy-Item -Path "$ScriptPath" + "$MSI" -Destination "$TargetServerPath"
Copy-Item -Path "$ScriptPath" + "$MSBuildScript" -Destination "$TargetServerPath"
}
Invoke-Command -scriptblock $misScript
#Step2 : Execute the powershell script ExecuteBizTalkAppMSI.ps1 remotely on the target server
#using syntax... invoke-command -computer $MachineName -command { $TargetServerPath + ExecuteBizTalkAppMSI.ps1 }"
}
I run this script from the Powershell ISE with the following line:
Copy-Deploy "C:\Builds\3\x.Int.MIS\SupportBTDF\Sources\x.Int.MIS\Dev\V1.0\Src\Solutions\MIS\x.Int.MIS.Deployment\" "ExecuteBizTalkAppMSI.ps1" "bin\debug\x.Int.MIS-3.0.0.msi" "x.Int.MIS.Deployment.btdfproj" "\\d-vasbiz01\BizTalkDeployment"
I then get the following error:
Cannot bind parameter 'ForegroundColor'. Cannot convert value "C:\Builds\3\x.Int.MIS\SupportBTDF\Sources\x.Int.MIS\Dev\V1.0\Src\Solutions\MIS\x.Int.MIS.Deployment\,ExecuteBizTalkAppMSI.ps1" to type "System.ConsoleColor" due to invalid enumeration values. Specify one of the following enumeration values and try again. The possible enumeration values are "Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White".At C:\Builds\3\x.Int.MIS\SupportBTDF\Sources\x.Int.MIS\Dev\V1.0\Src\Solutions\MIS\x.Int.MIS.Deployment\CopyDeployScriptThenExecute.ps1:79 char:16
Could anyone please tell me where I went wrong?
The line :
Write-Host " Path+Filename = {0}({1})" -f $ScriptPath, $PowershellDeploymentScript
is the problem.
Write it like this:
Write-Host (" Path+Filename = {0}({1})" -f $ScriptPath, $PowershellDeploymentScript)
The -f was being taken as the -ForegroundColor parameter than the string format operator.
Wrap the message in parens. You can also use variable expansion and embed the variables without using the -format operator (like you did in other write-host calls):
Write-Host " Path+Filename = $ScriptPath($PowershellDeploymentScript)"