2

I'm trying to export several information from a collection of objects into CSV files.

For this purpose I always have the following pipe :

 $users | < my filters/grouping/selects and expands> | Export-CSV ...

Instead of copy/paste these lines I would prefer to have an hashtable with as a key the CSV filename and and as a value the part between < ... >

So I did this :

$scriptblocks = @{"NonCompliantMail"={ ? {-not ([bool]($_.mail -as [Net.Mail.MailAddress])) } };
              "NonCompliantSAM"= { ? { ($_.samaccountname.Trim().Length - $_.samaccountname.Length) -ne 0 }};
              "MissingSN" = { ? {[string]::IsNullOrWhiteSpace($_.sn) } };
              "MissingGivenName" = { ? {[string]::IsNullOrWhiteSpace($_.givenname) } };
              "TrimSN" = { ? { (-not ([string]::IsNullOrWhiteSpace($_.sn))) -and (($_.sn.Trim().Length - $_.sn.Length) -ne 0) } };
              "TrimGivenName" = { ? { (-not ([string]::IsNullOrWhiteSpace($_.givenname))) -and (($_.givenname.Trim().Length - $_.givenname.Length) -ne 0) } }
              "MultipleEmails" = { group-object mail |? { $_.Count -gt 1 } | select -ExpandProperty Group | select mail,samaccountname }
              }

and I'm trying to execute it like that but it doesn't work :

$scriptblocks.getEnumerator() |% { $users | & $_.Value | Export-Csv -NoTypeInformation -Force -Encoding $encoding -Delimiter $delimiter -Path (Join-Path $scriptpath ($_.Key + ".csv")) } 

Any ideas on how to do it ?

Regards. JB

5
  • 1
    Replace $scriptblocks to $scriptblocks.GetEnumerator() Commented Feb 5, 2015 at 19:36
  • What's the point of this? The first thing you do in your ForEach loop is pipe $users so you have lost your $_ from $scriptblocks because it is now representative of the current iteration of $users Commented Feb 5, 2015 at 19:51
  • @PetSerAI : yes exact I forgot the GetEnumerator. Now the files are created but empty :/ Commented Feb 5, 2015 at 19:52
  • @TheMadTechnician : exact. I would need to keep both. The goal is to only have to add avoid 5 times the same lines (but I've got other cases so it's more 20/25) Commented Feb 5, 2015 at 19:56
  • Got it, I understand now and will have an answer for you shortly. You need to use a Switch loop. Writing a decent answer will take a few minutes but it's coming. Commented Feb 5, 2015 at 20:01

2 Answers 2

1

Ok, so a Switch is a loop that tests each record of an array against a set of filters. If the record passes the filter it it run against the following scriptblock. The filters can be a literal match, a RegEx match, or a scriptblock similar to a Where statement. For your needs we'll be using the last of those. Check out this example and see if it accomplishes what you are going for:

Switch($users){
    {-not ([bool]($_.mail -as [Net.Mail.MailAddress])) }{$_|Export-Csv -NoTypeInformation -Force -Encoding $encoding -Delimiter $delimiter -Path (Join-Path $scriptpath ("NonCompliantMail" + ".csv")) -append}
    {($_.samaccountname.Trim().Length - $_.samaccountname.Length) -ne 0 }{$_|Export-Csv -NoTypeInformation -Force -Encoding $encoding -Delimiter $delimiter -Path (Join-Path $scriptpath ("NonCompliantSAM" + ".csv")) -append}
    {[string]::IsNullOrWhiteSpace($_.sn) }{$_|Export-Csv -NoTypeInformation -Force -Encoding $encoding -Delimiter $delimiter -Path (Join-Path $scriptpath ("MissingSN" + ".csv")) -append}
    {[string]::IsNullOrWhiteSpace($_.givenname) }{$_|Export-Csv -NoTypeInformation -Force -Encoding $encoding -Delimiter $delimiter -Path (Join-Path $scriptpath ("MissingGivenName" + ".csv")) -append}
    {(-not ([string]::IsNullOrWhiteSpace($_.sn))) -and (($_.sn.Trim().Length - $_.sn.Length) -ne 0) }{$_|Export-Csv -NoTypeInformation -Force -Encoding $encoding -Delimiter $delimiter -Path (Join-Path $scriptpath ("TrimSN" + ".csv")) -append}
    {(-not ([string]::IsNullOrWhiteSpace($_.givenname))) -and (($_.givenname.Trim().Length - $_.givenname.Length) -ne 0) }{$_|Export-Csv -NoTypeInformation -Force -Encoding $encoding -Delimiter $delimiter -Path (Join-Path $scriptpath ("TrimGivenName" + ".csv")) -append}
}

That will run each user through the switch, and if it matches any of the conditions it will append it to the associated CSV file.

Edit: Ok, you didn't like Switch. If you really want to be able to execute scriptblocks in a ForEach-Object loop like that you can add parameters to your scriptblocks to allow piped data, but this doesn't completely solve your issue. I'll get to that in a moment. First, let's take your Group-Object mail option and set it up to accept input:

"MultipleEmails" = { group-object mail |? { $_.Count -eq 1 } | select -ExpandProperty Group | select mail,samaccountname }

becomes

"MultipleEmails" = {Param([Parameter(ValueFromPipeline=$True)][Object[]]$Users);$Users| group-object mail |? { $_.Count -eq 1 } | select -ExpandProperty Group | select mail,samaccountname }

I added Param([Parameter(ValueFromPipeline=$True)][Object[]]$Users);$Users| to the beginning of the scriptblock to do this. You can add that to the beginning of each scriptblock and they should all run similarly.

Then we have to force $users to be passed to it as an array by wrapping it as such: (,$users)
That allows this:

(,$users)|& $scriptblocks["multipleemails"]

That provides the output that you would expect it to. All that's left is to put that in your ForEach for $ScriptBlocks, along with keeping track of your current scriptblock:

$scriptblocks.keys|%{$sb=$_;(,$users)|& $scriptblocks["$sb"]}

That outputs everything from all of the scriptblocks. The only issue you now have is that you have no way to specify what CSV to output to. But this at least answers your original question.

Sign up to request clarification or add additional context in comments.

5 Comments

It can be a solution but I don't always have a where. I also have this for example : $users | group-object mail |? { $_.Count -gt 1 } | select -ExpandProperty Group | select mail,samaccountname | export ... And I wanted to just have the "middle" part variable, the export csv is always the same. That's why I wanted to factor these.
Except that the export csv is not always the same. You want the export to be to different files dependent on the middle portion, so really the only thing that stays the same is that you are piping $users in, and expect to be able to export to a variety of different csv files at the end. You can't just have the middle change and the end stay the same, what if a user matches multiple criteria? Export-Csv does not support writing to multiple files in one pass. The -Path parameter accepts a string, not an array of strings.
Yes in my case I loop throught each user in each scriptblock. It's quite fast actually, more than your switch (I did a test of 10 iterations on 42000 and more users) 10% faster using $users | ... | Export-CSV .... I kind of like your method, but It doesn't work for some of my scriptblocks. (just added one of them on initial question)
Ok, that should do it for you. Updated answer to allow you to loop through scriptblocks. Now I'm pretty sure you just have an issue with output.
Thanks! I think I'll stick with original method fully expanded with $users | ...| export-csv ... on each line as it become too ugly and it doesn't fully resolve the issue of the export csv filename
1

The problem with this approach is that it is inefficient: you have to loop through all your $users once for each output file.

Another approach would be to use a switch statement to get around this (psuedocode to follow):

$file1 = "NonCompliantMail.csv"
$file2 = "NonCompliantSAM.csv"
$file3 = "MissingSN.csv"
$file4 = "MissingGivenName.csv"
$file5 = "TrimSN.csv"
$file6 = "TrimGivenName.csv"
$file7 = "UsersWhoDontMatchAnything.csv"

function WriteUserDataToFile
{

    param
    (
        $User,
        $OutFile
    )

    "Process the $User object to append to $OutFile"
}

switch ($users) 
{ 
    ("User matching criteria 1") {WriteUserDataToFile $_ $file1}
    ("User matching criteria 2") {WriteUserDataToFile $_ $file2} 
    ("User matching criteria 3") {WriteUserDataToFile $_ $file3} 
    ("User matching criteria 4") {WriteUserDataToFile $_ $file4} 
    ("User matching criteria 5") {WriteUserDataToFile $_ $file5}
    ("User matching criteria 6") {WriteUserDataToFile $_ $file6}
    default {WriteUserDataToFile $_ $file7}
}

So the users are matched against your criteria one-by-one and when a match is made the function to append that user's data to the file for that type of match is called.

Hope that is a fitting suggestion.

2 Comments

You just posted the same answer I did 20 minutes after me, except you don't explain switch, added in a fairly useless function, and only posted psuedocode. You did this after the OP already said that the switch cmdlet didn't really accomplish what he wants.
The problems of working on an answer and posting it without refreshing the page. The function is there to simplify the scriptblocks: I always try to reduce repetition in that way. Apologies to JB if this is not suitable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.