Powershell CSV Variable Issue - powershell

I am having a problem with passing a variable into a CSV. I need to pass an email for a spreadsheet showing all skills. It's the same email for each skill. I just want the $email to populate my csv. It does not pass and only shows the $email instead of the test#test.com in the column.
I am new at powershell so any guidance is greatly appreciated. Thank you.
------------here is my script-------------------------
Add-Content -Path C:\temp\test.csv -Value '"User Name","Skill Name","Level"'
$Email = "test#test.com"
$agent = #(
}
'"$Email","T1","4"'
'"$Email","T2","6"'
'"$Email","T3","7"'
'"$Email","Training","1"'
'"$Email","Supervisor","8"'
)
$agent | foreach { Add-Content -Path C:\temp\temp.csv -Value $_ }

Because you are new to Powershell, I'm showing you two alternative ways of framing the problem. These might help you get used to some of the features of powershell.
$Email = 'test#test.com'
$mytext = #"
"User Name","Skill Name","Level"
"$Email","T1","4"
"$Email","T2","6"
"$Email","T3","7"
"$Email","Training","1"
"$Email","Supervisor","8"
"#
$mytext | Out-file Mycsv.csv
Here, I just set up the Email variable, then create one big here string with the header and the five data records in it. Because I used double quotes on the here string, the variable $Email will be detected inside of it. A here string with single quotes would not have behaved correctly.
Then, I pass $mytext through a pipeline one line at a time, and Out-file collects all this into a file.
Here's the second approach:
$Email = 'test#test.com'
$myarray = #(
[PsCustomobject]#{"User Name" = $Email; "Skill Name" = "T1"; "Level" = 4}
[PsCustomobject]#{"User Name" = $Email; "Skill Name" = "T2"; "Level" = 6}
[PsCustomobject]#{"User Name" = $Email; "Skill Name" = "T3"; "Level" = 7}
[PsCustomobject]#{"User Name" = $Email; "Skill Name" = "Training"; "Level" = 1}
[PsCustomobject]#{"User Name" = $Email; "Skill Name" = "Supervisor"; "Level" = 8}
)
$myarray | Export-Csv myothercsv.csv
Here, I set up the variable Email, then create an array of custom objects, each with the same named properties.
Then I pass the array through a pipeline to Export-Csv which converts everything to Csv format. It's worth noting that Export-Csv V5 throws in a line that says #TYPE in it. This is not hard to eliminate, using the notype parameter if desired. It's also worth noting that the double quotes in the output file were all added in by Export-csv, and weren't copies of the double quotes in the script.
Edit. Pipelines are a surprisingly easy and flexible way of getting things done in powershell. For this reason, cmdlets like Out-File and Export-Csv are built to work well with pipelines supplying a stream of input. A lot of loop control, initialization, and finalization busy work is being handled behind the scenes by PS.

Related

Powershell I would like to look for a Name in a CSV and return their email in the textbox

Is it possible to have a CSV file with everyone's Name and Email address. then what I would like to do within a GUI type just their name into a textbox and it will return an email address.
I am currently using an if statement as I have tested it with just one user.
$text = $textbox1.text
if($text -like Jake){
$textbox4.visible = $true
$textbox4.text = "Jake#hotmail.com"
}else{
$textbox4.visible = $false
}
}
obviously this is not the best idea ^^^ that's why I think have a separate CSV and it can us that as the look up. any help with this would be awesome
It was much simpler then I thought.
$text = $textbox1.text
$csv = Import-Csv "path to csv"
ForEach($User in $CSV){
If($user.Name -eq '$text'){
$text = '$user.outcome'
}
}
My CSV headers were Name and Outcome

Set-ADuser extensionAttribute won't work but things like title will

I am writing a simple script that takes an already created user and updates an attribute based on what the admin put in.
The code works just fine if I replace extensionAttribute with for example title or something like that, but it won't with extensionAttributes.
I have tried a few things and other extensionAttributes but the code is so simple and it works with other Attributes. I am guess extensionAttributes require a bit more in the code that I am missing.
$name = Read-Host "AD Logon Name"
$key = Read-Host "Azure Key"
Set-ADUser $name -extensionAttribute6 $key -PassThru
Set-ADUser : A parameter cannot be found that matches parameter name 'extensionAttribute6'
Even though it exists it is not finding it.
Set-ADUser has a limited set of parameters covering the most commonly used attributes in AD. However, given the sheer amount of existing attributes and the fact that the AD schema is extensible, an attempt to have all attributes represented as parameters just wouldn't be feasible.
For attributes that are not represented as parameters use the parameter -Add or -Replace with a hashtable argument.
Set-ADUser $name -Replace #{'extensionAttribute6' = $key} -PassThru
Old thread, but this worked for me:
Import-Csv -Path "C:\data\12345.csv" |ForEach-Object {
Set-ADUser $_.samAccountName -replace #{
description = "$($_.description)"
extensionAttribute1 = "$($_.extensionAttribute1)"
extensionAttribute3 = "$($_.extensionAttribute3)"
initials = "$($_.initials)";
#additionalAttributeName = "$($_.additionalAttributeName)"
#additionalAttributeName = "$($_.additionalAttributeName)"
#additionalAttributeName = "$($_.additionalAttributeName)"
#additionalAttributeName = "$($_.additionalAttributeName)"
#additionalAttributeName = "$($_.additionalAttributeName)"
}
}
The top row of your .csv file would look like the following for this example:
samAccountname,description,extensionAttribute1,extensionAttribute3,initials

Capture multiple variables

I currently have a powershell script that allows me to enter in the details for users one at a time which can then be used as variables later on. At present the script calls for each input and then saves for as a variable, this is then repeated 10 times.
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null
$NameOne = [Microsoft.VisualBasic.Interaction]::InputBox("Enter UserName")
$firstname,$surname = $NameOne -split("\.")
$NameTwo = [Microsoft.VisualBasic.Interaction]::InputBox("Enter UserName")
$firstname,$surname = $NameTwo -split("\.")
Is there a way to shorten the script to both allow usernames to be input and stored but to also move on to the next part of the script when an InputBox has no data input?
Thanks
Tom
Use a while loop:
$Users = while(($Username = [Microsoft.VisualBasic.Interaction]::InputBox("Enter UserName")).Trim() -ne "")
{
$firstname,$surname = $Username -split '\.'
New-Object psobject -Property #{
Firstname = $firstname
Surname = $surname
Username = $Username
}
}
When the user inputs nothing or whitespace, the loop will exit, otherwise it'll create a new "User object" that will end up in the $Users array

Expressions are only allowed as the first element of a pipeline

I'm new at writing in powershell but this is what I'm trying to accomplish.
I want to compare the dates of the two excel files to determine if one is newer than the other.
I want to convert a file from csv to xls on a computer that doesn't have excel. Only if the statement above is true, the initial xls file was copied already.
I want to copy the newly converted xls file to another location
If the file is already open it will fail to copy so I want to send out an email alert on success or failure of this operation.
Here is the script that I'm having issues with. The error is "Expressions are only allowed as the first element of a pipeline." I know it's to do with the email operation but I'm at a loss as to how to write this out manually with all those variables included. There are probably more errors but I'm not seeing them now. Thanks for any help, I appreciate it!
$CSV = "C:filename.csv"
$LocalXLS = "C:\filename.xls"
$RemoteXLS = "D:\filename.xls"
$LocalDate = (Get-Item $LocalXLS).LASTWRITETIME
$RemoteDate = (Get-Item $RemoteXLS).LASTWRITETIME
$convert = "D:\CSV Converter\csvcnv.exe"
if ($LocalDate -eq $RemoteDate) {break}
else {
& $convert $CSV $LocalXLS
$FromAddress = "email#address.com"
$ToAddress = "email#address.com"
$MessageSubject = "vague subject"
$SendingServer = "mail.mail.com"
$SMTPMessage = New-Object System.Net.Mail.MailMessage $FromAddress, $ToAddress, $MessageSubject, $MessageBody
$SMTPClient = New-Object System.Net.Mail.SMTPClient $SendingServer
$SendEmailSuccess = $MessageBody = "The copy completed successfully!" | New-Object System.Net.Mail.SMTPClient mail.mail.com $SMTPMessage
$RenamedXLS = {$_.BaseName+(Get-Date -f yyyy-MM-dd)+$_.Extension}
Rename-Item -path $RemoteXLS -newname $RenamedXLS -force -erroraction silentlycontinue
If (!$error)
{ $SendEmailSuccess | copy-item $LocalXLS -destination $RemoteXLS -force }
Else
{$MessageBody = "The copy failed, please make sure the file is closed." | $SMTPClient.Send($SMTPMessage)}
}
You get this error when you are trying to execute an independent block of code from within a pipeline chain.
Just as a different example, imagine this code using jQuery:
$("div").not(".main").console.log(this)
Each dot (.) will chain the array into the next function. In the above function this breaks with console because it's not meant to have any values piped in. If we want to break from our chaining to execute some code (perhaps on objects in the chain - we can do so with each like this:
$("div").not(".main").each(function() {console.log(this)})
The solution is powershell is identical. If you want to run a script against each item in your chain individually, you can use ForEach-Object or it's alias (%).
Imagine you have the following function in Powershell:
$settings | ?{$_.Key -eq 'Environment' } | $_.Value = "Prod"
The last line cannot be executed because it is a script, but we can fix that with ForEach like this:
$settings | ?{$_.Key -eq 'Environment' } | %{ $_.Value = "Prod" }
This error basically happens when you use an expression on the receiving side of the pipeline when it cannot receive the objects from the pipeline.
You would get the error if you do something like this:
$a="test" | $a
or even this:
"test" | $a
I don't know why are trying to pipe everywhere. I would recommend you to learn basics about Powershell pipelining. You are approaching it wrong. Also, I think you can refer to the link below to see how to send mail, should be straight forward without the complications that you have added with the pipes : http://www.searchmarked.com/windows/how-to-send-an-email-using-a-windows-powershell-script.php

Powershell to Validate Email addresses

I'm trying to get Powershell to validate email addresses using Regex and put email addresses into good and bad csv files. I can get it to skip one line and write to file, but cannot get it to target the email addresses and validate them, then write lines to good and bad files. I can do it in C# and JavaScript, but have never done it in Powershell. I know this can be done, but not sure how.
Here is what I have so far:
Function IsValidEmail {
Param ([string] $In)
# Returns true if In is in valid e-mail format.
[system.Text.RegularExpressions.Regex]::IsMatch($In,
"^([\w-\.]+)#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|
(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$");
}
## Now we need to check the original file for invalid and valid emails.**
$list = Get-Content C:\Emails\OriginalEmails\emailAddresses.csv
# This way we also use the foreach loop.
##======= Test to see if the file exists ===========
if (!(Test-Path "C:\Emails\ValidEmails\ValidEmails.csv")) {
New-Item -path C:\Emails\ValidEmails -name ValidEmails.csv -type
"file" # -value "my new text"
Write-Host "Created new file and text content added"
}
else {
## Add-Content -path C:\Share\sample.txt -value "new text content"
Write-Host "File already exists and new text content added"
}
if (!(Test-Path "C:\Emails\InValidEmails\InValidEmails.csv")) {
New-Item -path C:\Emails\InValidEmails -name InValidEmails.csv -type
"file" # -value "my new text"
Write-Host "Created new file and text content added"
}
else {
# Add-Content -path C:\Emails\ValidEmails -value "new text content"
Write-Host "File already exists and new text content added"
}
#$Addresses = Import-Csv "C:\Data\Addresses.csv" -Header
Name, Address, PhoneNumber | Select -Skip 1
$EmailAddressImp = Import-Csv
"C:\Emails\OriginalEmails\emailAddresses.csv" -Header
FirstName, LastName, Email, Address, City, State, ZipCode | Select
FirstName, LastName, Email, Address, City, State, ZipCode -Skip 1
I'm validating the third column "Email" in the original csv file and trying to write out the whole row to file (good file, bad file). Not sure how to buffer either doing this.
ForEach ($emailAddress in $list) {
if (IsValidEmail($emailAddress)) {
"Valid: {0}" -f $emailAddress
Out-File -Append C:\Emails\ValidEmails\ValidEmails.csv -Encoding UTF8
$EmailAddressImp | Export-Csv "C:\Emails\ValidEmails\ValidEmails.csv"
-NoTypeInformation
}
else {
"Invalid: {0}" -f $emailAddress
Out-File -Append C:\Emails\InValidEmails\InValidEmails.csv -
Encoding UTF8
$EmailAddressImp | Export-Csv
"C:\Emails\InValidEmails\InValidEmails.csv" -NoTypeInformation
}
}
I'm trying to get Powershell to validate email addresses using Regex
Don't!
I would recommend against this. Accurately validating email addresses using regular expressions can be much more difficult than you might think.
Let's have a look at your regex pattern:
^([\w-\.]+)#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$
In it's current form it incorrectly validates .#domain.tld.
On the other hand, it doesn't validate unicode-encoded internationalized domain names, like user#☎.com (yes, that's a valid email address)
Instead of trying to find or construct a perfect email validation regex pattern, I would use the MailAddress class for validation instead:
function IsValidEmail {
param([string]$EmailAddress)
try {
$null = [mailaddress]$EmailAddress
return $true
}
catch {
return $false
}
}
If the input string is a valid email address, the cast to [mailaddress] will succeed and the function return $true - if not, the cast will result in an exception, and it returns $false.
When exporting the data, I'd consider collecting all the results at once in memory and then writing it to file once, at the end.
If you're using PowerShell version 2 or 3, you can do the same with two passes of Where-Object:
$EmailAddresses = Import-Csv "C:\Emails\OriginalEmails\emailAddresses.csv" -Header FirstName, LastName, Email, Address, City, State, ZipCode | Select -Skip 1
$valid = $list |Where-Object {IsValidEmail $_.Email}
$invalid = $list |Where-Object {-not(IsValidEmail $_.Email)}
If you're using PowerShell version 4.0 or newer, I'd suggest using the .Where() extension method in Split mode:
$EmailAddresses = Import-Csv "C:\Emails\OriginalEmails\emailAddresses.csv" -Header FirstName, LastName, Email, Address, City, State, ZipCode | Select -Skip 1
$valid,$invalid = $list.Where({IsValidEmail $_.Email}, 'Split')
before exporting to file:
if($valid.Count -gt 0){
$valid |Export-Csv "C:\Emails\ValidEmails\ValidEmails.csv" -NoTypeInformation
}
if($invalid.Count -gt 0){
$invalid |Export-Csv "C:\Emails\ValidEmails\InvalidEmails.csv" -NoTypeInformation
}
You can just use the -match operator, instead of calling into the [Regex] class. Here's a simple example, without any wrapper function:
$EmailRegex = '^([\w-\.]+)#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$'
$EmailList = #('a#a.com', 'b#b.co', 'm.a#example.il')
foreach ($Email in $EmailList) {
$DidItMatch = $Email -match $EmailRegex
if ($DidItMatch) {
# It matched! Do something.
}
else {
# It didn't match
}
}
FYI, when you use the -match operator, if it returns boolean $true, then PowerShell automatically populates a built-in (aka. "automatic") variable called $matches. To avoid unexpected behavior, you might want to reset this variable to $null during each iteration, or just wrap it in a function as you did in your original example. This will keep the variable scoped to the function level, as long as you don't declare it in one of the parent scopes.
Once you've validated the e-mail address, you can append it to your existing CSV file, using:
Export-Csv -Append -FilePath filepath.csv -InputObject $Email
For efficiency with the available filesystem resources, you'll probably want to buffer a few e-mail addresses in memory, before appending them to your target CSV file.
# Initialize a couple array buffers
$ValidEmails = #()
$InvalidEmails = #()
if ($ValidEmails.Count -gt 50) {
# Run the CSV export here
}
if ($Invalid.Count -gt $50) {
# Run the CSV export here
}
If you need further help, can you please edit your question and clarify what isn't working for you?
Each of the current top 2 answers here has one significant deficiency:
#Trevor's answer would do just fine, until you supply it this:
John Doe <johndoe#somewhere.com>
#Mathias' answer preaches about accommodating exceptional (yet valid) addresses such as those with non-ASCII or no TLD suffix. The following addresses all validate successfully with the [mailaddress] casting:
olly#somewhere | olly#somewhere. | olly#somewhere...com etc
If, like me, you will not be entertaining these edge cases into your email databases, then a combination of both ideas might prove more useful, like so:
function IsValidEmail {
param([string]$Email)
$Regex = '^([\w-\.]+)#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$'
try {
$obj = [mailaddress]$Email
if($obj.Address -match $Regex){
return $True
}
return $False
}
catch {
return $False
}
}
Perhaps there is a performance overhead with creating $obj for every email address on a possibly long mailing list. But I guess that's another matter.
You can use the mailaddress type to ensure it meets RFC, but you will likely still want to make sure the domain is valid:
Resolve-DnsName -Name ('vertigoray#example.com' -as [mailaddress]).Host -Type 'MX'
Works well as a validation script for a function parameter:
function Assert-FromEmail {
param(
[Parameter(Mandatory = $true)]
[ValidateScript({ Resolve-DnsName -Name $_.Host -Type 'MX' })]
[mailaddress]
$From
)
Write-Output $From
}
Output examples of that function on success:
PS > Assert-FromEmail -From vertigoray#example.com
DisplayName User Host Address
----------- ---- ---- -------
vertigoray example.com vertigoray#example.com
Output examples of that function on failure:
PS > Assert-FromEmail -From vertigoray#example..com
Assert-FromEmail : Cannot validate argument on parameter 'From'. The " Resolve-DnsName -Name $_.Host -Type 'MX' "validation script for the argument with value "vertigoray#example..com" did not return a result of True. Determine why the validation script failed, and then try the command again.
At line:1 char:24
+ Assert-FromEmail -From vertigoray#example..com
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Assert-FromEmail], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Assert-FromEmail
Here is one to try I wrote up and tested and has not failed me in any environment to date. Not, saying it won't in someone else's, but for me, it's been 100%.
$SomeEmailAddresses = #'
From:JoeBob#yahoo.com,Tom TheCat tcat#snailmail.net,jerry#snailmail.net
To:TulaJane#hotmail.com;JF#gmail.com;tiger#outlook.com;
Doug Tompson DTompson#icloud.com
MailTo:BobsYourUncle#protonmail.com;
johnny.bravo#yahoo.co.uk
'#
(((Select-String -InputObject $SomeEmailAddresses `
-Pattern '\w+#\w+\.\w+|\w+\.\w+#\w+\.\w+\.\w+' `
-AllMatches).Matches).Value)
Rsults
JoeBob#yahoo.com
tcat#snailmail.net
jerry#snailmail.net
TulaJane#hotmail.com
JF#gmail.com
tiger#outlook.com
DTompson#icloud.com
BobsYourUncle#protonmail.com
johnny.bravo#yahoo.co.uk
#postanote
This common email formatting fails
$SomeEmailAddresses = #'
First A. Last first.a.last#gmail.com.
'#
(((Select-String -InputObject $SomeEmailAddresses -Pattern '\w+#\w+\.\w+|\w+\.\w+#\w+\.\w+\.\w+'
-AllMatches).Matches).Value)
Here is the code I use.
The regex does not support the following because the major email players do not support.
Domains as IP addresses.
Space and special characters "(),:;<>#[] inside a quoted string in local-part.
Comments within parentheses in local-part.
$email = "^(?(?=^(?:([a-zA-Z0-9_!#$%&'+-/=?^{|}~]+|[a-zA-Z0-9_!#$%&'*+\-\/=?^{|}~].[a-zA-Z0-9_!#$%&'+-/=?^{|}~][\.a-zA-Z0-9_!#$%&'*+\-\/=?^{|}~]))#[a-zA-Z0-9.-]{1,63}$)[a-zA-Z0-9_.!#$%&'*+-/=?^`{|}~]{1,63}#[a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]{2,})+)$"
$email -match $regexPattern