Issue with code not tentering the if loop in PowerShell - switch-statement

I think this is dumb question but needs attention, cause I got stuck with it for some long time. I have some PowerShell code which needs help. In this code, I pass the input '1' for switch condition and 'True' for the next prompt, it is entering the 1st if condition, but not entering the 2nd if condition, even if I pass $prd's input as foo.
Have tried by declaring the variable $prd="Global", still not working.
[String]$prd = Read-Host "Enter the software to update"
[bool]$upg = $true
$param = Read-Host -Prompt "Enter the input for upgrade"
switch ($param) {
0 {
# Some condition
}
1 {
[String]$sftup = "Going for sftwre updte"
$sftd_up = Read-Host -Prompt $sftup
if ($sftd_up -eq $upg) { # -------->1st if
Write-Host "foo upgrade"
if ($prd -eq "foo") { # ----------> 2nd if
$nameprd = New-Item -Path "D:\Temp" -ItemType "directory"
}
}
}
}

It works for me. (The new-item line I put in single quotes.) The third answer could be any string.
Enter the software to update: foo
Enter the input for upgrade: 1
Going for sftwre updte: true
foo upgrade
$nameprd = New-Item -Path "D:\Temp" -ItemType "directory"

Related

Powershell function in while loop doesn't output result first time but does output second time,twice

As you can see I have 5 options to select once the script runs. When I press 1, option 1 selected" gets printed but the function get_all_tags doesn't get called out and doesn't print list of tags. Second time around it prints the results from the function but repeats the result twice.
The weird thing about this is that the its inconsistent. I would run the script and in my first try pressing 1, sometimes, I would get the result from the function get_all_tags .When all the functions are tested on their own, it works perfectly fine. The while loop seems to have caused something irregular within and not sure how I can fix it.
$options=0
$iteration=0
while($options -ne 5)
{
$options = read-host -prompt "
Press
1. to search for all the tags running on Azure Virtual Machine(s)
2. to search for resource using tag name
3. to search for specific resource
4. to search for value of a tag name
5. to exit
"
if ($options -eq 1){
write-host "option 1 selected"
get_all_tags
#give option to exit or go back to the main menu
$iteration++
$iteration
}
elseif ($options -eq 2){
get_resource_with_tag_name $inputTagName
#give option to exit or go back to the main menu
}
elseif ($options -eq 3){
get_resource_tags $inputResource
#give option to exit or go back to the main menu
}
elseif ($options -eq 4){
get_keys_value $inputTagKey
#give option to exit or go back to the main menu
}
}
Function get_resource_tags($resourceName)
{
$inputResource = read-host -prompt 'Write a resource name you wish to search with all associated tags'
return (get-azresource -ResourceGroupName $inputResource).Tags
}
Function get_resource_with_tag_name($tagName){
$inputTagName = read-host -prompt 'Write a tag name you wish to search associated with resource'
return (get-AzResource -TagName $inputTagName).Name
}
Function get_all_tags{
return get-aztag
}
Function get_keys_value($tagKeyName){
$inputTagKey = read-host -prompt 'Write a tag name'
$result = (get-aztag -Detailed $inputTagKey).Values
$final = $result | ft -property #{n="Tag Value";e={$_.name}},count
return $final
}
PowerShell is an interpreter language as opposed to a compiler language. Hence, the location of your Functions is messing with your results. They should be declared before they are being called.
In your case, during the 1st run, the while loop is not aware of the functions defined below it. Once you run it completely, the functions are loaded in memory and hence, the second run will probably give you the intended results.
thanks #sid for responding and pointing out the difference. Despite having made the changes, Options 1 and 3 still do that same whilst 2 and 4 always print out results.
Function get_all_tags{
return get-aztag
}
Function get_resource_with_tag_name($tagName){
$inputTagName = read-host -prompt 'Write a tag name you wish to search associated with resource'
return (get-AzResource -TagName $inputTagName).Name
}
Function get_resource_tags($resourceName)
{
$inputResource = read-host -prompt 'Write a resource name you wish to search with all associated tags'
$iteration++
$iteration
return (get-azresource -ResourceGroupName $inputResource).Tags
}
Function get_keys_value($tagKeyName){
$inputTagKey = read-host -prompt 'Write a tag name'
$result = (get-aztag -Detailed $inputTagKey).Values
$final = $result | ft -property #{n="Tag Value";e={$_.name}},count
return $final
}
$options=0
$iteration=0
while($options -ne 5)
{
$options = read-host -prompt "
Press
1. to search for all the tags running on Azure Virtual Machine(s)
2. to search for resource using tag name
3. to search for specific resource
4. to search for value of a tag name
5. to exit
"
if ($options -eq 1){
write-host "option 1 selected"
get_all_tags
#give option to exit or go back to the main menu
}
elseif ($options -eq 2){
get_resource_with_tag_name $inputTagName
#give option to exit or go back to the main menu
}
elseif ($options -eq 3){
write-host "hello"
get_resource_tags $inputResource
#give option to exit or go back to the main menu
}
elseif ($options -eq 4){
get_keys_value $inputTagKey
#give option to exit or go back to the main menu
}
}

How to make sure string is being given a proper format

I'm coding a progress bar for my script, and I've run into an issue where a string is being fed data in an incorrect format.
foreach ($User in $Users) {
#Set UserPrincipalName for current user
$upn = $User
#Display progress bar
$percentage = [math]::Round($loopcount / $maxcount *100)
$message = "Applying changes for $upn ($loopcount of $maxcount)" -f $percentage
Write-Progress -Activity $message -ErrorAction SilentlyContinue -PercentComplete ($percentage) -Status "Progress $percentage% :"
This is the main part of the code that makes up the progress bar (there's more, but that's irrelevant). It works great when it is being fed user identities (via $User) like "delat" and "damo", but when it gets to a user called "DiscoverySearch {D919BA05-46A6-415f-80AD-XXXXXXXXXXXXXXXX}" it understandably throws a "Error formatting a string" error.
How do I go about solving this issue?
Edit: Below is the source of $User, in case that's needed.
$MailboxRegionList = Get-Mailbox | Get-MailboxRegionalConfiguration
$Users += $MailboxRegionList | Select-Object -ExpandProperty Identity
This happens, as -f $percentage is not doing what you'd expect. It is not going to format the message as percentage a value.
$message = "Applying changes for $upn ($loopcount of $maxcount)" -f $percentage
The format operator, -f will fill in a value in string. The placeholder is to be marked with curly brackets {}, which your string doesn't usually have. When the $user is DiscoverySearch {D919BA05-46A6-415f-80AD-XXXXXXXXXXXXXXXX}, it contains curly brackets. Now, the curly brackets should follow .Net composite formatting syntax, and a GUID in account name does not do that.
As for a fix, try
$message = "Applying changes for $upn ($loopcount of $maxcount) $percentage"

Remove known Excel passwords with PowerShell

I have this PowerShell code that loops through Excel files in a specified directory; references a list of known passwords to find the correct one; and then opens, decrypts, and saves that file to a new directory.
But it's not executing as quickly as I'd like (it's part of a larger ETL process and it's a bottleneck). At this point I can remove the passwords faster manually as the script takes ~40 minutes to decrypt 40 workbooks while referencing a list of ~50 passwords.
Is there a cmdlet or function (or something) that's missing which would speed this up, an overlooked flaw in the processing, or is PowerShell, perhaps, just not the right tool for this job?
Original Code (updated code can be found below):
$ErrorActionPreference = "SilentlyContinue"
CLS
# Paths
$encrypted_path = "C:\PoShTest\Encrypted\"
$decrypted_Path = "C:\PoShTest\Decrypted\"
$original_Path = "C:\PoShTest\Originals\"
$password_Path = "C:\PoShTest\Passwords\Passwords.txt"
# Load Password Cache
$arrPasswords = Get-Content -Path $password_Path
# Load File List
$arrFiles = Get-ChildItem $encrypted_path
# Create counter to display progress
[int] $count = ($arrfiles.count -1)
# Loop through each file
$arrFiles| % {
$file = get-item -path $_.fullname
# Display current file
write-host "Processing" $file.name -f "DarkYellow"
write-host "Items remaining: " $count `n
# Excel xlsx
if ($file.Extension -eq ".xlsx") {
# Loop through password cache
$arrPasswords | % {
$passwd = $_
# New Excel Object
$ExcelObj = $null
$ExcelObj = New-Object -ComObject Excel.Application
$ExcelObj.Visible = $false
# Attempt to open file
$Workbook = $ExcelObj.Workbooks.Open($file.fullname,1,$false,5,$passwd)
$Workbook.Activate()
# if password is correct - Save new file without password to $decrypted_Path
if ($Workbook.Worksheets.count -ne 0) {
$Workbook.Password=$null
$savePath = $decrypted_Path+$file.Name
write-host "Decrypted: " $file.Name -f "DarkGreen"
$Workbook.SaveAs($savePath)
# Close document and Application
$ExcelObj.Workbooks.close()
$ExcelObj.Application.Quit()
# Move original file to $original_Path
move-item $file.fullname -Destination $original_Path -Force
}
else {
# Close document and Application
write-host "PASSWORD NOT FOUND: " $file.name -f "Magenta"
$ExcelObj.Close()
$ExcelObj.Application.Quit()
}
}
}
$count--
# Next File
}
Write-host "`n Processing Complete" -f "Green"
Updated code:
# Get Current EXCEL Process ID's so they are not affected but the scripts cleanup
# SilentlyContinue in case there are no active Excels
$currentExcelProcessIDs = (Get-Process excel -ErrorAction SilentlyContinue).Id
$a = Get-Date
$ErrorActionPreference = "SilentlyContinue"
CLS
# Paths
$encrypted_path = "C:\PoShTest\Encrypted"
$decrypted_Path = "C:\PoShTest\Decrypted\"
$processed_Path = "C:\PoShTest\Processed\"
$password_Path = "C:\PoShTest\Passwords\Passwords.txt"
# Load Password Cache
$arrPasswords = Get-Content -Path $password_Path
# Load File List
$arrFiles = Get-ChildItem $encrypted_path
# Create counter to display progress
[int] $count = ($arrfiles.count -1)
# New Excel Object
$ExcelObj = $null
$ExcelObj = New-Object -ComObject Excel.Application
$ExcelObj.Visible = $false
# Loop through each file
$arrFiles| % {
$file = get-item -path $_.fullname
# Display current file
write-host "`n Processing" $file.name -f "DarkYellow"
write-host "`n Items remaining: " $count `n
# Excel xlsx
if ($file.Extension -like "*.xls*") {
# Loop through password cache
$arrPasswords | % {
$passwd = $_
# Attempt to open file
$Workbook = $ExcelObj.Workbooks.Open($file.fullname,1,$false,5,$passwd)
$Workbook.Activate()
# if password is correct, remove $passwd from array and save new file without password to $decrypted_Path
if ($Workbook.Worksheets.count -ne 0)
{
$Workbook.Password=$null
$savePath = $decrypted_Path+$file.Name
write-host "Decrypted: " $file.Name -f "DarkGreen"
$Workbook.SaveAs($savePath)
# Added to keep Excel process memory utilization in check
$ExcelObj.Workbooks.close()
# Move original file to $processed_Path
move-item $file.fullname -Destination $processed_Path -Force
}
else {
# Close Document
$ExcelObj.Workbooks.Close()
}
}
}
$count--
# Next File
}
# Close Document and Application
$ExcelObj.Workbooks.close()
$ExcelObj.Application.Quit()
Write-host "`nProcessing Complete!" -f "Green"
Write-host "`nFiles w/o a matching password can be found in the Encrypted folder."
Write-host "`nTime Started : " $a.ToShortTimeString()
Write-host "Time Completed : " $(Get-Date).ToShortTimeString()
Write-host "`nTotal Duration : "
NEW-TIMESPAN –Start $a –End $(Get-Date)
# Remove any stale Excel processes created by this script's execution
Get-Process excel -ErrorAction SilentlyContinue | Where-Object{$currentExcelProcessIDs -notcontains $_.id} | Stop-Process
If nothing else I do see one glaring performance issue that should be easy to address. You are opening a new excel instance for testing each individual password for each document. 40 workbooks with 50 passwords mean you have opened 2000 Excel instances one at a time.
You should be able to keep using the same one without a functionality hit. Get this code out of your inner most loop
# New Excel Object
$ExcelObj = $null
$ExcelObj = New-Object -ComObject Excel.Application
$ExcelObj.Visible = $false
as well as the snippet that would close the process. It would need to be out of the loop as well.
$ExcelObj.Close()
$ExcelObj.Application.Quit()
If that does not help enough you would have to consider doing some sort of parallel processing with jobs etc. I have a basic solution in a CodeReview.SE answer of mine doing something similar.
Basically what it does is run several excels at once where each one works on a chunk of documents which runs faster than one Excel doing them all. Just like I do in the linked answer I caution the automation of Excel COM with PowerShell. COM objects don't always get released properly and locks can be left on files or processes.
You are looping for all 50 passwords regardless of success or not. That means you could find the right password on the first go but you are still going to try the other 49! Set a flag in the loop to break that inner loop when that happens.
As far as the password logic goes you say that
At this point I can remove the passwords faster manually since the script takes ~40 minutes
Why can you do it faster? What do you know that the script does not. I don't see you being able to out perform the script but doing exactly what it does.
With what I see another suggestion would be to keep/track successful passwords and associated file name. So that way when it gets processed again you would know the first password to try.
This solution uses the modules ImportExcel for easier working with Excel files, and PoshRSJob for multithreaded processing.
If you do not have these, install them by running:
Install-Module ImportExcel -scope CurrentUser
Install-Module PoshRSJob -scope CurrentUser
I've raised an issue on the ImportExcel module GitHub page where I've proposed a solution to open encrypted Excel files. The author may propose a better solution (and consider the impact across other functions in the module, but this works for me). For now, you'll need to make a modification to the Import-Excel function yourself:
Open: C:\Username\Documents\WindowsPowerShell\Modules\ImportExcel\2.4.0\ImportExcel.psm1 and scroll to the Import-Excel function. Replace:
[switch]$DataOnly
With
[switch]$DataOnly,
[String]$Password
Then replace the following line:
$xl = New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $stream
With the code suggested here. This will let you call the Import-Excel function with a -Password parameter.
Next we need our function to repeatedly try and open a singular Excel file using a known set of passwords. Open a PowerShell window and paste in the following function (note: this function has a default output path defined, and also outputs passwords in the verbose stream - make sure no-one is looking over your shoulder or just remove that if you'd prefer):
function Remove-ExcelEncryption
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
[String]
$File,
[Parameter(Mandatory=$false)]
[String]
$OutputPath = 'C:\PoShTest\Decrypted',
[Parameter(Mandatory=$true)]
[Array]
$PasswordArray
)
$filename = Split-Path -Path $file -Leaf
foreach($Password in $PasswordArray)
{
Write-Verbose "Attempting to open $file with password: $Password"
try
{
$ExcelData = Import-Excel -path $file -Password $Password -ErrorAction Stop
Write-Verbose "Successfully opened file."
}
catch
{
Write-Verbose "Failed with error $($Error[0].Exception.Message)"
continue
}
try
{
$null = $ExcelData | Export-Excel -Path $OutputPath\$filename
return "Success"
}
catch
{
Write-Warning "Could not save to $OutputPath\$filename"
}
}
}
Finally, we can run code to do the work:
$Start = get-date
$PasswordArray = #('dj7F9vsm','kDZq737b','wrzCgTWk','DqP2KtZ4')
$files = Get-ChildItem -Path 'C:\PoShTest\Encrypted'
$files | Start-RSJob -Name {$_.Name} -ScriptBlock {
Remove-ExcelEncryption -File $_.Fullname -PasswordArray $Using:PasswordArray -Verbose
} -FunctionsToLoad Remove-ExcelEncryption -ModulesToImport Import-Excel | Wait-RSJob | Receive-RSJob
$end = Get-Date
New-TimeSpan -Start $Start -End $end
For me, if the correct password is first in the list it runs in 13 seconds against 128 Excel files. If I call the function in a standard foreach loop, it takes 27 seconds.
To view which files were successfully converted we can inspect the output property on the RSJob objects (this is the output of the Remove-ExcelEncryption function where I've told it to return "Success"):
Get-RSJob | Select-Object -Property Name,Output
Hope that helps.

PowerShell: What's wrong with "... ${_.Name} ..."?

Why am i not able to use $_ inside of text as one can use other variables?
Get-ChildItem -Path $path -filter *.mp3 | foreach {
$count++;
write-host "File${count}=${_.Name}";
}
I know I can write it this way:
Get-ChildItem -Path $path -filter *.mp3 | foreach {
$count++;
write-host "File${count}=$($_.Name)";
}
When you write ${_.Name} you're actually asking for the variable named _.Name, not the Name property of the $_ variable.
PS > ${_.Name} = "test"
PS > Get-Variable _*
Name Value
---- -----
_.Name test
The reason $($_.Name) works is because $() means "process this first", so you can specify whatever you want inside. In this case you just specified a variable name and the property you wanted, but you could also make it more complex like:
PS > $a = 1
PS > "A's value is 1(true or false?): $(if($a -eq 1) { "This is TRUE!" } else { "This is FALSE!" })"
A's value is 1(true or false?): This is TRUE!
PS > $a = 2
PS > "A's value is 1(true or false?): $(if($a -eq 1) { "This is TRUE!" } else { "This is FALSE!" })"
A's value is 1(true or false?): This is FALSE!

Strange error with NewWebPage - SP 2010 Foundation

I'm having a strange issue here using the NewWebPage xml command in SP 2010 Foundation. The below code works does not work
cls
Write-Host "Production Manager v8.0 Deployment Utility" -ForegroundColor red
Write-Host ""
#Starting the script. Lets see if we can find the configuration and map files first.
Write-Host -NoNewline "Checking for the Configuration.xml, PageMap.xml and PageTemplate.xml files: " -ForegroundColor white
if((Test-Path "Configuration.xml") -and (Test-Path "PageMap.xml") -and (Test-Path "PageTemplate.xml"))
{
Write-Host "FOUND" -ForegroundColor green
}
else
{
Write-Host "NOT FOUND" -ForegroundColor red
Write-Host "Check for the necessary files and try again."
Write-Host ""
Write-Host ""
exit
}
Write-Host "Reading Configuration.xml"
[xml]$Configuration = Get-Content Configuration.xml
Write-Host "Reading PageMap.xml"
[xml]$PageMap = Get-Content PageMap.xml
Write-Host "Reading from Production Manager Site: "$Configuration.Configuration.SiteConfiguration.SiteURL
#Some variables necessary for the loop
$pageCreationLoopIterations = 0
$pageLayout = ""
$pageTitle = ""
$createPageCommand = '<?xml version="1.0" encoding="UTF-8"?><Method ID="0,NewWebPage"><SetList Scope="Request">' + $productionManagerLibrary.ID + '</SetList><SetVar Name="Cmd">NewWebPage</SetVar><SetVar Name="ID">New</SetVar><SetVar Name="Type">WebPartPage</SetVar><SetVar Name="WebPartPageTemplate">' + $pageLayout + '</SetVar><SetVar Name="Overwrite">true</SetVar><SetVar Name="Title">MyPage</SetVar></Method>';
#Beginning the loop
Write-Host "Running through the PageMap file"
foreach($Page in $PageMap.Pages.Page)
{
$web = Get-SPWeb $Configuration.Configuration.SiteConfiguration.SiteURL
$productionManagerLibrary = $web.Lists | Where { $_.Title -eq "Production Manager" }
$pageName = if($Page.SelectSingleNode("PageName")) { $Page.PageName } else { $Configuration.Configuration.PageConfiguration.DefaultPageName }
$pageLayout = if($Page.SelectSingleNode("PageLayout")) { $Page.PageLayout } else { $Configuration.Configuration.PageConfiguration.DefaultPageLayout }
Write-Host 'Creating Page ' $pageName
$web.ProcessBatchData($createPageCommand)
}
But this one works just fine every time I run it:
$url = "http://mpm8/mpm";
$listname = "Production Manager"
$web = Get-SPWeb $url
$pagesLibrary = $web.Lists | Where { $_.Title -eq "Production Manager" }
$pageLayout = 8
$cmd = '<?xml version="1.0" encoding="UTF-8"?><Method ID="0,NewWebPage"><SetList Scope="Request">' + $pagesLibrary.ID + '</SetList><SetVar Name="Cmd">NewWebPage</SetVar><SetVar Name="ID">New</SetVar><SetVar Name="Type">WebPartPage</SetVar><SetVar Name="WebPartPageTemplate">' + $pageLayout + '</SetVar><SetVar Name="Overwrite">true</SetVar><SetVar Name="Title">MyPage</SetVar></Method>';
$web.ProcessBatchData($cmd)
I really cannot see anything different between the two scripts. The error I get running the first one is:
<Result ID="0,NewWebPage" Code="-2130575350">
<ErrorText>Invalid URL Parameter.
The URL provided contains an invalid Command or Value. Please check the URL again. </ErrorText></Result>
Can you help me on this one? Maybe I cannot run this thing out a foreach loop? :(
Thanks!
You have set the $createPageCommand first (before the loop), then set the variables that it needs second (in the loop).
If you run this in the ISE and step through (ie debug it), you can see the variables and their values, otherwise simply emit the values of the variables to screen as you run through the loop to ensure it is setting them correctly.
So, in your example, place the $createPageCommand within the loop and after $productionManagerLibrary is set. Then, immediately before $web.ProcessBatchData($createPageCommand) you should emit the value of $createPageCommand to ensure it is OK.
I've only eye balled this without running it, but please us know if this is the reason!

Resources