Powershell function issue - function

Error I'm getting is the following
Missing closing '}' in statement block.
I'm using this to automate adding user to a .dat file which has hundreds of user's for one of our systems. I was task with taking this part over and well I don't like to do anything manually. So I wrote a script to do 95% of it for me and it removes user's just fine. But this function I'm trying here adds the user in 2 different specific places to keep it nice and net. I'm still learning powershell so any direction would be great and you wont hurt my feelings I'm no pro nor novice here.
Function Add{
$username = Read-Host "User to add to REP01-02 Printing"
$LOCreportADD1 = "\\rep01\E$\orant\Report60\Server\cgicmd.dat"
$LOCreportADD2 = "\\rep02\e$\orant\Report60\Server\cgicmd.dat"
$Username1 = $username+": "
$prd = "prd: "
$userid = "userid="
$henation = "/henation#rmsprd %*"
$Text1 = get-content $LOCreportADD1
$Text2 = get-content $LOCreportADD2
$NewText1 = #()
foreach ($Line in $Text1) {
if ($Line -eq "Insert new user1") {
$Line = $Line.Replace("Insert new user1 \", "Insert new user1 \")
$NewText1 += $Line
$NewText1 += $username1+$userid+$username+$henation
}
Elseif ($Line -eq "New User2") {
$Line = $Line.Replace("New User2 \", "New User2 \")
$NewText12 += $Line
$NewText12 += $username+$prd+$userid+$username+$henation
}
$NewText1 | Set-Content $LOCreportADD1
}

The error says it all. You are missing a } at the end to close the function.
Tips is to put your script in an editor (Like Notepad++) that can handle {} matching and it's easy to see if you miss a {/}.

The error says it all. You are missing a '}' at the end to close the foreach block..

Function Add{
$username = Read-Host "User to add to REP01-02 Printing"
$LOCreportADD1 = "\\rep01\E$\orant\Report60\Server\cgicmd.dat"
$LOCreportADD2 = "\\rep02\e$\orant\Report60\Server\cgicmd.dat"
$Username1 = $username+": "
$prd = "prd: "
$userid = "userid="
$henation = "/henation#rmsprd %*"
$Text1 = get-content $LOCreportADD1
$Text2 = get-content $LOCreportADD2
$NewText1 = #()
foreach ($Line in $Text1) {
if ($Line -eq "Insert new user1") {
$Line = $Line.Replace("Insert new user1 \", "Insert new user1 \")
$NewText1 += $Line
$NewText1 += $username1+$userid+$username+$henation
}
Elseif ($Line -eq "New User2") {
$Line = $Line.Replace("New User2 \", "New User2 \")
$NewText12 += $Line
$NewText12 += $username+$prd+$userid+$username+$henation
} # <---- This is your missing brace, to close out the Else condition
}
$NewText1 | Set-Content $LOCreportADD1
}

In the Powershell ISE you can highlight a '}'. It will then highlight the bracket it forms a block of code with. Highlight it, then right click it. Makes it easier to find your problem.

Related

Script to Enforce MFA failing to grab dataset from servers

function Enforce-MFA($exclude){
Connect-MsolService
$excludedUsers = 'admin','admin2','admin3','admin4' + $exclude
$excluded = ($excludedUsers | ForEach-Object { [regex]::Escape($_) }) -join '|'
$st = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationRequirement
$st.RelyingParty = "*"
$st.State = "Enforced"
$sta = #($st)
$array = (Get-MsolUser | Where-Object { $_.DisplayName -notmatch $excluded }).UserPrincipalName
ForEach ($user in $array)
{
Set-MsolUser -UserPrincipalName $user -StrongAuthenticationRequirements $sta
Write-Host "Complete"
}
}
The general function is to grab a list of objects, exclude certain objects, and Enforce MFA for the remaining objects. This script seemed to work without any issue last week, but this week, I'm getting no data from the Array variable. I was working on a lot of different changes and I'm thinking I may have messed something up in the process, but I'm just not seeing it. What did I mess up or what am I not seeing?
You forgot to add -Credential $cred to the Connect-MsolService.
You should create that connection first and take it out of the function.
function Enforce-MFA {
$excludeTheseUsers = 'admin', 'user1', 'user2' # etc.
# for using the regex `-notmatch` operator later, you need to combine the entries with the regex OR sign ('|'),
# but you need to make sure to escape special characters some names may contain
$excludes = ($excludeTheseUsers | ForEach-Object { [regex]::Escape($_) }) -join '|'
# create the StrongAuthenticationRequirement object just once, to use on all users
$st = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationRequirement
$st.RelyingParty = "*"
$st.State = "Enabled"
$sta = #($st)
# get an array of UserPrincipalNames
$array = (Get-MsolUser | Where-Object { $_.DisplayName -notmatch $excludes }).UserPrincipalName
foreach ($user in $array) {
Set-MsolUser -UserPrincipalName $user -StrongAuthenticationRequirements $sta
}
Write-Host "Enforcing MFA Complete"
}
# ask for credentials to make the connection
$cred = Get-Credential -Message 'Please enter your credentials to connect to Azure Active Directory'
Connect-MsolService -Credential $cred
As for your loop, try something like this:
# enter an endless loop
while($true) {
$var = Read-Host -Prompt "Enter the corresponding number: 1: Enforce 2: Enable 3: Disable 4: Exit"
switch($var){
1 { Enforce-MFA }
2 { Enable-MFA }
3 { Disable-MFA }
4 { exit }
default{ "Please choose either 1, 2 ,3 or 4" }
}
}

RunSpacePool output CSV contains blank rows

I am using this amazing answer and got RunSpacePools to output a CSV file but my CSV file has blank rows and I just cannot figure out where the blank rows are coming from.
The blank lines are shown in Notepad as ,,,
IF(Get-Command Get-SCOMAlert -ErrorAction SilentlyContinue){}ELSE{Import-Module OperationsManager}
"Get Pend reboot servers from prod"
New-SCOMManagementGroupConnection -ComputerName ProdServer1
$AlertData = get-SCOMAlert -Criteria "Severity = 1 AND ResolutionState < 254 AND Name = 'Pending Reboot'" | Select NetbiosComputerName
"Get Pend reboot servers from test"
#For test information
New-SCOMManagementGroupConnection -ComputerName TestServer1
$AlertData += Get-SCOMAlert -Criteria "Severity = 1 AND ResolutionState < 254 AND Name = 'Pending Reboot'" | Select NetbiosComputerName
"Remove duplicates"
$AlertDataNoDupe = $AlertData | Sort NetbiosComputerName -Unique
$scriptblock = {
Param([string]$server)
$csv = Import-Csv D:\Scripts\MaintenanceWindow2.csv
$window = $csv | where {$_.Computername -eq "$server"} | % CollectionName
$SCCMWindow = IF ($window){$window}ELSE{"NoDeadline"}
$PingCheck = Test-Connection -Count 1 $server -Quiet -ErrorAction SilentlyContinue
IF($PingCheck){$PingResults = "Alive"}
ELSE{$PingResults = "Dead"}
Try{$operatingSystem = Get-WmiObject Win32_OperatingSystem -ComputerName $server -ErrorAction Stop
$LastReboot = [Management.ManagementDateTimeConverter]::ToDateTime($operatingSystem.LastBootUpTime)
$LastReboot.DateTime}
Catch{$LastReboot = "Access Denied!"}
#create custom object as output for CSV.
[PSCustomObject]#{
Server=$server
MaintenanceWindow=$SCCMWindow
Ping=$PingResults
LastReboot=$LastReboot
}#end custom object
}#script block end
$RunspacePool = [RunspaceFactory]::CreateRunspacePool(100,100)
$RunspacePool.Open()
$Jobs =
foreach ( $item in $AlertDataNoDupe )
{
$Job = [powershell]::Create().
AddScript($ScriptBlock).
AddArgument($item.NetbiosComputerName)
$Job.RunspacePool = $RunspacePool
[PSCustomObject]#{
Pipe = $Job
Result = $Job.BeginInvoke()
}
}
Write-Host 'Working..' -NoNewline
Do {
Write-Host '.' -NoNewline
Start-Sleep -Milliseconds 500
} While ( $Jobs.Result.IsCompleted -contains $false)
Write-Host ' Done! Writing output file.'
Write-host "Output file is d:\scripts\runspacetest4.csv"
$(ForEach ($Job in $Jobs)
{ $Job.Pipe.EndInvoke($Job.Result) }) |
Export-Csv d:\scripts\runspacetest4.csv -NoTypeInformation
$RunspacePool.Close()
$RunspacePool.Dispose()
After trial and error, I ended up working with this method of run space pools to get close. Looking closer, I found the output was polluted by WMI's extra whitespaces.
To solve this, I ended up using the following within the ScriptBlock's Try statement.
$LastReboot = [Management.ManagementDateTimeConverter]::ToDateTime `
($operatingSystem.LastBootUpTime).ToString().Trim()
Now the data returned is all single line as desired.
-Edit to comment on WMI's extra whitespaces in output. See this question for more details.
Consider the following method to return a computer's last reboot timestamp. Note you can format the string as needed, see this library page for more info.
$os = (gwmi -Class win32_operatingsystem).LastBootUpTime
[Management.ManagementDateTimeConverter]::ToDateTime($os)
Observe the whitespaces, which can be removed by converting the output to a string then using Trim() to remove the whitespaces.

Replace blank characters from a file line by line

I would like to be able to find all blanks from a CSV file and if a blank character is found on a line then should appear on the screen and I should be asked if I want to keep the entire line which contains that white space or remove it.
Let's say the directory is C:\Cr\Powershell\test. In there there is one CSV file abc.csv.
Tried doing it like this but in PowerShell ISE the $_.PSObject.Properties isn't recognized.
$csv = Import-Csv C:\Cr\Powershell\test\*.csv | Foreach-Object {
$_.PSObject.Properties | Foreach-Object {$_.Value = $_.Value.Trim()}
}
I apologize for not includding more code and what I tried more so far but they were silly attempts since I just begun.
This looks helpful but I don't know exactly how to adapt it for my problem.
Ok man here you go:
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Retain line."
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Delete line."
$n = #()
$f = Get-Content .\test.csv
foreach($item in $f) {
if($item -like "* *"){
$res = $host.ui.PromptForChoice("Title", "want to keep this line? `n $item", [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no), 0)
switch ($res)
{
0 {$n+=$item}
1 {}
}
} else {
$n+=$item
}
}
$n | Set-Content .\test.csv
if you have questions please post in the comments and i will explain
Get-Content is probably a better approach than Import-Csv, because that'll allow you to check an entire line for spaces instead of having to check each individual field. For fully automated processing you'd just use a Where-Object filter to remove non-matching lines from the output:
Get-Content 'C:\CrPowershell\test\input.csv' |
Where-Object { $_ -notlike '* *' } |
Set-Content 'C:\CrPowershell\test\output.csv'
However, since you want to prompt for each individual line that contains spaces you need a ForEach-Object (or a similiar construct) and a nested conditional, like this:
Get-Content 'C:\CrPowershell\test\input.csv' | ForEach-Object {
if ($_ -notlike '* *') { $_ }
} | Set-Content 'C:\CrPowershell\test\output.csv'
The simplest way to prompt a user for input is Read-Host:
$answer = Read-Host -Prompt 'Message'
if ($answer -eq 'y') {
# do one thing
} else {
# do another
}
In your particular case you'd probably do something like this for any matching line:
$anwser = Read-Host "$_`nKeep the line? [y/n] "
if ($answer -ne 'n') { $_ }
The above checks if the answer is not n to make removal of the line a conscious decision.
Other ways to prompt for user input are choice.exe (which has the additional advantage of allowing a timeout and a default answer):
choice.exe /c YN /d N /t 10 /m "$_`nKeep the line"
if ($LastExitCode -ne 2) { $_ }
or the host UI:
$title = $_
$message = 'Keep the line?'
$yes = New-Object Management.Automation.Host.ChoiceDescription '&Yes'
$no = New-Object Management.Automation.Host.ChoiceDescription '&No'
$options = [Management.Automation.Host.ChoiceDescription[]]($yes, $no)
$answer = $Host.UI.PromptForChoice($title, $message, $options, 1)
if ($answer -ne 1) { $_ }
I'm leaving it as an exercise for you to integrate whichever prompting routine you chose with the rest of the code.

remove only blanks line by line with user input

Yesterday I asked this question. Now I would like to do almost the same exercise with a small change: if there is a blank character on a line (go line by line in the CSV) ask the user if the blank character should be removed or not; the difference is, ONLY the blank and not the whole line.
The code which works for my previous question is:
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Retain line."
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Delete line."
$n = #()
$f = Get-Content .\test.csv
foreach ($item in $f) {
if($item -like "* *"){
$res = $host.ui.PromptForChoice("Title", "want to keep this line? `n $item", [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no), 0)
switch ($res) {
0 {$n+=$item}
1 {}
}
} else {
$n+=$item
}
}
$n | Set-Content .\test.csv
What I think I should use is the Trim() function to achieve this.
So, I think I should modify in the if clause like below (aplogies for the silly syntax mistakes which I might do):
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Retain blank."
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Delete blank."
$n = #()
$f = Get-Content .\test.csv
foreach ($item in $f) {
if ($item -like "* *") {
$res = $host.ui.PromptForChoice("Title", "want to keep the blank on this line? `n $item", [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no), 0)
switch ($res) {
0 {$n+=$item.Trim()}
1 {}
}
} else {
$n+=$item.Trim()
}
}
$n | Set-Content .\test.csv
This runs, but still deteles the line, so it shouldn't matter if I trimmed it or not first, I need to fix it so it will be kept or trimmed but not discarded .
EDIT:
Adjusting the switch ($res) like this doesn't work:
switch ($res) {
0 {$n+=$item.Trim()}
1 {$n+=$item}
}
} else {
$n+=$item.Trim()
}
Trim() without parameter removes all whitespace (not just spaces) from beginning and end of a string. You can't use it for removing spaces anywhere else in a string. Instead use the -replace operator:
$_ -replace ' '
Note that this time you need to output the unmodified string not only if it doesn't contain a space, but also if the user chooses to keep the existing space(s).

Import CSV and updating specific lines

So I have a script that runs at logon to search for PST's on a users machine, then copies them to a holding area waiting for migration.
When the search/copy is complete it outputs to a CSV that looks something like this:
Hostname,User,Path,Size_in_MB,Creation,Last_Access,Copied
COMP1,user1,\\comp1\c$\Test PST.pst,20.58752,08/12/2015,08/12/2015,Client copied
COMP1,user1,\\comp1\c$\outlook\outlook.pst,100,08/12/2015,15,12,2015,In Use
The same logon script has an IF to import the CSV if the copied status is in use and makes further attempts at copying the PST into the holding area. If it's successful it exports the results to the CSV file.
My question is, is there anyway of getting it to either amend the existing CSV changing the copy status? I can get it to add the new line to the end, but not update.
This is my 'try again' script:
# imports line of csv where PST file is found to be in use
$PST_IN_USE = Import-CSV "\\comp4\TEMPPST\PST\$HOSTNAME - $USER.csv" | where { $_.copied -eq "In Use" }
ForEach ( $PST_USE in $PST_IN_USE )
{ $NAME = Get-ItemProperty $PST_IN_USE.Path | select -ExpandProperty Name
$NEW_NAME = $USER + "_" + $PST_IN_USE.Size_in_MB + "_" + $NAME
# attempts to copy the file to the pst staging area then rename it.
TRY { Copy-Item $PST_IN_USE.Path "\\comp4\TEMPPST\PST\$USER" -ErrorAction SilentlyContinue
Rename-Item "\\comp4\TEMPPST\PST\$USER\$NAME" -NewName $NEW_NAME
# edits the existing csv file replacing "In Use" with "Client Copied"
$PST_IN_USE.Copied -replace "In Use","Client Copied"
} # CLOSES TRY
# silences any errors.
CATCH { }
$PST_IN_USE | Export-Csv "\\comp4\TEMPPST\PST\$HOSTNAME - $USER.csv" -NoClobber -NoTypeInformation -Append
} # CLOSES ForEach ( $PST_USE in $PST_IN_USE )
This is the resulting CSV
Hostname,User,Path,Size_in_MB,Creation,Last_Access,Copied
COMP1,user1,\\comp1\c$\Test PST.pst,20.58752,08/12/2015,08/12/2015,Client copied
COMP1,user1,\\comp1\c$\outlook\outlook.pst,100,08/12/2015,15,12,2015,In Use
COMP1,user1,\\comp1\c$\outlook\outlook.pst,100,08/12/2015,15,12,2015,Client copied
It's almost certainly something really simple, but if it is, it's something I've yet to come across in my scripting. I'm mostly working in IF / ELSE land at the moment!
If you want to change the CSV file, you have to write it completely again, not just appending new lines. In your case this means:
# Get the data
$data = Import-Csv ...
# Get the 'In Use' entries
$inUse = $data | where Copied -eq 'In Use'
foreach ($x in $inUse) {
...
$x.Copied = 'Client Copied'
}
# Write the file again
$data | Export-Csv ...
The point here is, you grab all the lines from the CSV, modify those that you process and then write the complete collection back to the file again.
I've cracked it. It's almost certainly a long winded way of doing it, but it works and is relatively clean too.
#imports line of csv where PST file is found to be in use
$PST_IN_USE = Import-CSV "\\comp4\TEMPPST\PST\$HOSTNAME - $USER.csv" | where { $_.copied -eq "In Use" }
$PST_IN_USE | select -ExpandProperty path | foreach {
# name of pst
$NAME = Get-ItemProperty $_ | select -ExpandProperty Name
# size of pst in MB without decimals
$SIZE = Get-ItemProperty $_ | select -ExpandProperty length | foreach { $_ / 1000000 }
# path of pst
$PATH = $_
# new name of pst when copied to the destination
$NEW_NAME = $USER + "_" + $SIZE + "_" + $NAME
TRY { Copy-Item $_ "\\comp4\TEMPPST\PST\$USER" -ErrorAction SilentlyContinue
TRY { Rename-Item "\\comp4\TEMPPST\PST\$USER\$NAME" -NewName $NEW_NAME -ErrorAction SilentlyContinue | Out-Null }
CATCH { $NEW_NAME = "Duplicate exists" }
$COPIED = "Client copied" }
CATCH { $COPIED = "In use" ; $NEW_NAME = " " }
$NEW_FILE = Test-Path "\\comp4\TEMPPST\PST\$HOSTNAME - $USER 4.csv"
IF ( $NEW_FILE -eq $FALSE )
{ "Hostname,User,Path,Size_in_MB,Creation,Last_Access,Copied,New_Name" |
Set-Content "\\lccfp1\TEMPPST\PST\$HOSTNAME - $USER 4.csv" }
"$HOSTNAME,$USER,$PATH,$SIZE,$CREATION,$LASTACCESS,$COPIED,$NEW_NAME" |
Add-Content "\\comp4\TEMPPST\PST\$HOSTNAME - $USER 4.csv"
} # CLOSES FOREACH #
$a = Import-CSV "\\comp4\TEMPPST\PST\$HOSTNAME - $USER.csv" | where { $_.copied -ne "in use" }
$b = Import-Csv "\\comp4\TEMPPST\PST\$HOSTNAME - $USER 4.csv"
$a + $b | export-csv "\\comp4\TEMPPST\PST\$HOSTNAME - $USER 8.csv" -NoClobber -NoTypeInformation
Thanks for the help. Sometimes it takes a moments break and a large cup of coffee to see things a different way.