I have a powershell script, I want that it'll always return the number with 2 decimal place (I.E 1 = 1.00, 1.1 = 1.10, 1.11 = 1.11)
I've managed to do it for 1 and 1.11, but I can think of a solution that wont take too much space for 1.1.
Thats what I got:
$Result = $shippingprice % 2
IF ($Result -ne 1 -and $Result -ne 0)
{
$shippingpricestring = "$" + $shippingprice.ToString()
}
else {
$shippingpricestring = "$" + ($shippingprice | % { '{0:0.00}' -f $_ })
}
$Result = $itemprice % 2
IF ($Result -ne 1 -and $Result -ne 0)
{
$itempricestring = "$" + $itemprice.ToString()
}
else {
$itempricestring = "$" + ($itemprice | % { '{0:0.00}' -f $_ })
}
IF ($Result -ne 1 -and $Result -ne 0)
{
$taxstring = "$" + $tax.ToString()
}
else {
$taxstring = "$" + ($tax | % { '{0:0.00}' -f $_ })
}
$Result = $finalrice % 2
IF ($Result -ne 1 -and $Result -ne 0)
{
$finalricestring = "$" +$finalrice.ToString()
}
else {
$finalricestring = "$" + ($finalrice | % { '{0:0.00}' -f $_ })
}
$shippingpricestring = "$" + $shippingprice.ToString("#.##")
Scripting Guy Reference: Hey Scripting Guy
$shippingprice = 17.1111
$ItemPrice = 4.1211
$tax = 94.919
$finalrice =10.3211
$Result = $shippingprice % 2
IF ($Result -ne 1 -and $Result -ne 0)
{
$shippingpricestring = "$" + $shippingprice.ToString("#.##")
}
else {
$shippingpricestring = "$" + ($shippingprice | % { '{0:0.00}' -f $_ })
}
Write-Output $shippingpricestring
IF ($Result -ne 1 -and $Result -ne 0)
{
$itempricestring = "$" + $itemprice.ToString("#.##")
}
else {
$itempricestring = "$" + ($itemprice | % { '{0:0.00}' -f $_ })
}
Write-Output $itempricestring
IF ($Result -ne 1 -and $Result -ne 0)
{
$taxstring = "$" + $tax.ToString("#.##")
}
else {
$taxstring = "$" + ($tax | % { '{0:0.00}' -f $_ })
}
Write-Output $taxstring
$Result = $finalrice % 2
IF ($Result -ne 1 -and $Result -ne 0)
{
$finalricestring = "$" +$finalrice.ToString("#.##")
}
else {
$finalricestring = "$" + ($finalrice | % { '{0:0.00}' -f $_ })
}
Write-Output $finalricestring
RESULT
$17.11
$4.12
$94.92
$10.32
*** The above assumes that all the variables have more than 2 decimal spaces. If say $tax = 9.9 $taxstring would appear as 9.9 and NOT as 9.90
If you want to format the number with .ToString you can go with .ToString(format)
Example
$list = 1, 1.1, 1.111, 1.1111
$list | %{$_.ToString("0.00")}
Output
1.00
1.10
1.11
1.11
Related
So I've pieced together a menu that contains a title and options you can select from. Selecting an item is done by using the arrow keys. I want to have the selected item highlighted so you can tell which item you are currently selecting. I'm new to PowerShell and am familiar as to how to change colors with write-host but in this example I am clueless. The line in question that I believe I need to inject the color options into is the one that starts with $Width. I'd really appreciate some insight! This is my last hiccup before beginning to add actual code!
Function Create-Menu (){
Param(
[Parameter(Mandatory=$True)][String]$MenuTitle,
[Parameter(Mandatory=$True)][array]$MenuOptions
)
$MaxValue = $MenuOptions.count-1
$Selection = 0
$EnterPressed = $False
Clear-Host
While($EnterPressed -eq $False){
For ($i=0; $i -le $MaxValue; $i++){
$Width = if($Title){$Length = $Title.Length;$Length2 = $MenuOptions|%{$_.length}|Sort -Descending|Select -First 1;$Length2,$Length|Sort -Descending|Select -First 1}else{$MenuOptions|%{$_.length}|Sort -Descending|Select -First 1}
$Buffer = if(($Width*1.5) -gt 78){(78-$width)/2}else{$width/4}
if($Buffer -gt 6){$Buffer = 6}
$MaxWidth = $Buffer*2+$Width+$($MenuOptions.count).length
$Menu = #()
$Menu += "╔"+"═"*$maxwidth+"╗"
if($MenuTitle){
$Menu += "║"+" "*[Math]::Floor(($maxwidth-$MenuTitle.Length)/2)+$MenuTitle+" "*[Math]::Ceiling(($maxwidth-$MenuTitle.Length)/2)+"║"
$Menu += "╟"+"─"*$maxwidth+"╢"
}
For($i=1;$i -le $MenuOptions.count;$i++){
$Item = "$i`. "
$Menu += "║"+" "*$Buffer+$Item+$MenuOptions[$i-1]+" "*($MaxWidth-$Buffer-$Item.Length-$MenuOptions[$i-1].Length)+"║"
}
$Menu += "╚"+"═"*$maxwidth+"╝"
$menu
}
$KeyInput = $host.ui.rawui.readkey("NoEcho,IncludeKeyDown").virtualkeycode
Switch($KeyInput){
13{
$EnterPressed = $True
$script:Selection = "$Selection"
Clear-Host
break
}
38{
If ($Selection -eq 0){
$Selection = $MaxValue
} Else {
$Selection -= 1
}
Clear-Host
break
}
40{
If ($Selection -eq $MaxValue){
$Selection = 0
} Else {
$Selection +=1
}
Clear-Host
break
}
Default{
Clear-Host
}
}
}
}
#MainMenu
Function MainMenu (){
Create-Menu -MenuTitle "Tool" -MenuOptions "Lookup","Prep","Tools","Settings","Cleanup and Exit"
If($script:Selection -eq 0) {Lookup}
If($script:Selection -eq 1) {PrepMenu}
If($script:Selection -eq 2) {ToolsMenu}
If($script:Selection -eq 3) {SettingsMenu}
If($script:Selection -eq 4) {CleanupAndExit}
}
MainMenu
pause
The code outputs the following:
╔═════════════════════════╗
║ Tool ║
╟─────────────────────────╢
║ 1. Lookup ║
║ 2. Prep ║
║ 3. Tools ║
║ 4. Settings ║
║ 5. Cleanup and Exit ║
╚═════════════════════════╝
You can add color in (I used Green, but that's up to you) with some minor adjustments to your code:
function Create-Menu {
Param(
[Parameter(Mandatory=$True)][String]$MenuTitle,
[Parameter(Mandatory=$True)][array]$MenuOptions
)
# test if we're not running in the ISE
if ($Host.Name -match 'ISE') {
Throw "This menu must be run in PowerShell Console"
}
$MaxValue = $MenuOptions.Count-1
$Selection = 0
$EnterPressed = $False
While(!$EnterPressed) {
# draw the menu
Clear-Host
for ($i = 0; $i -le $MaxValue; $i++){
[int]$Width = [math]::Max($MenuTitle.Length, ($MenuOptions | Measure-Object -Property Length -Maximum).Maximum)
[int]$Buffer = if (($Width * 1.5) -gt 78) { (78 - $width) / 2 } else { $width / 4 }
$Buffer = [math]::Min(6, $Buffer)
$MaxWidth = $Buffer * 2 + $Width + $MenuOptions.Count.ToString().Length
Write-Host ("╔" + "═" * $maxwidth + "╗")
# write the title if present
if (!([string]::IsNullOrWhiteSpace($MenuTitle))) {
$leftSpace = ' ' * [Math]::Floor(($maxwidth - $MenuTitle.Length)/2)
$rightSpace = ' ' * [Math]::Ceiling(($maxwidth - $MenuTitle.Length)/2)
Write-Host ("║" + $leftSpace + $MenuTitle + $rightSpace + "║")
Write-Host ("╟" + "─" * $maxwidth + "╢")
}
# write the menu option lines
for($i = 0; $i -lt $MenuOptions.Count; $i++){
$Item = "$($i + 1). "
$Option = $MenuOptions[$i]
$leftSpace = ' ' * $Buffer
$rightSpace = ' ' * ($MaxWidth - $Buffer - $Item.Length - $Option.Length)
$line = "║" + $leftSpace + $Item + $Option + $rightSpace + "║"
if ($Selection -eq $i) {
Write-Host $line -ForegroundColor Green
}
else {
Write-Host $line
}
}
Write-Host ("╚" + "═" * $maxwidth + "╝")
}
# wait for an accepted key press
do {
$KeyInput = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown').VirtualKeyCode
} while (13, 38, 40 -notcontains $KeyInput)
Switch($KeyInput){
13{
$EnterPressed = $True
return $Selection
}
38 {
$Selection--
if ($Selection -lt 0){ $Selection = $MaxValue }
break
}
40 {
$Selection++
if ($Selection -gt $MaxValue) { $Selection = 0 }
# or: $Selection = ($Selection + 1) % ($MaxValue + 1)
break
}
}
}
}
#MainMenu
function MainMenu {
$selected = Create-Menu -MenuTitle "Tools" -MenuOptions "Lookup","Prep","Tools","Settings","Cleanup and Exit"
switch ($selected) {
0 {"Lookup"}
1 {"PrepMenu"}
2 {"ToolsMenu"}
3 {"SettingsMenu"}
4 {"CleanupAndExit"}
}
}
MainMenu
Edit
Just for the fun of it, the above code uses Clear-Host before each redraw of the menu which results in a workable, but flickering menu.
Below the same menu, but this time it redraws without first clearing the console window resulting in a much smoother menu.
function Create-Menu {
Param(
[Parameter(Mandatory=$false)][string] $MenuTitle = $null,
[Parameter(Mandatory=$true)] [string[]]$MenuOptions
)
# test if we're not running in the ISE
if ($Host.Name -match 'ISE') {
Throw "This menu must be run in PowerShell Console"
}
$MaxValue = $MenuOptions.Count-1
$Selection = 0
$EnterPressed = $False
[console]::CursorVisible = $false # prevents cursor flickering
Clear-Host
while(!$EnterPressed) {
# draw the menu without Clear-Host to prevent flicker
[console]::SetCursorPosition(0,0)
for ($i = 0; $i -le $MaxValue; $i++){
[int]$Width = [math]::Max($MenuTitle.Length, ($MenuOptions | Measure-Object -Property Length -Maximum).Maximum)
[int]$Buffer = if (($Width * 1.5) -gt 78) { (78 - $width) / 2 } else { $width / 4 }
$Buffer = [math]::Min(6, $Buffer)
$MaxWidth = $Buffer * 2 + $Width + $MenuOptions.Count.ToString().Length
Write-Host ("╔" + "═" * $maxwidth + "╗")
# write the title if present
if (!([string]::IsNullOrWhiteSpace($MenuTitle))) {
$leftSpace = ' ' * [Math]::Floor(($maxwidth - $MenuTitle.Length)/2)
$rightSpace = ' ' * [Math]::Ceiling(($maxwidth - $MenuTitle.Length)/2)
Write-Host ("║" + $leftSpace + $MenuTitle + $rightSpace + "║")
Write-Host ("╟" + "─" * $maxwidth + "╢")
}
# write the menu option lines
for($i = 0; $i -lt $MenuOptions.Count; $i++){
$Item = "$($i + 1). "
$Option = $MenuOptions[$i]
$leftSpace = ' ' * $Buffer
$rightSpace = ' ' * ($MaxWidth - $Buffer - $Item.Length - $Option.Length)
$line = "║" + $leftSpace + $Item + $Option + $rightSpace + "║"
if ($Selection -eq $i) {
Write-Host $line -ForegroundColor Green
}
else {
Write-Host $line
}
}
Write-Host ("╚" + "═" * $maxwidth + "╝")
}
# wait for an accepted key press
do {
$KeyInput = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown').VirtualKeyCode
} while (13, 38, 40 -notcontains $KeyInput)
Switch($KeyInput){
13{
$EnterPressed = $True
[console]::CursorVisible = $true # reset the cursors visibility
return $Selection
}
38 {
$Selection--
if ($Selection -lt 0){ $Selection = $MaxValue }
break
}
40 {
$Selection++
if ($Selection -gt $MaxValue) { $Selection = 0 }
# or: $Selection = ($Selection + 1) % ($MaxValue + 1)
break
}
}
}
}
#MainMenu
function MainMenu {
$selected = Create-Menu -MenuTitle "Tools" -MenuOptions "Lookup","Prep","Tools","Settings","Cleanup and Exit"
switch ($selected) {
0 {"Lookup"}
1 {"PrepMenu"}
2 {"ToolsMenu"}
3 {"SettingsMenu"}
4 {"CleanupAndExit"}
}
}
MainMenu
I have INI file and I want to get specific section. The items in the section that I choose are 24 items. I want to use all the item to write in a file. I tried this, It works, but it looks like bad way to write 24 times to do the process. Is there any other way to do that more beautiful? The section of my INI file like this
Input ini:
[Code]
A1=12,34,56
A2=23,45,67
A3=34,56,78,9,10
...
A24=a1,b2,c3,d4,e5
Script:
Function F_ML
{
$FilePath = "C:\Users\File.ini"
$section = "Code"
$R_1 = "A1"
$R_2 = "A2"
$R_3 = "A3"
$R_4 = "A4"
$R_5 = "A5"
$R_6 = "A6"
$R_7 = "A7"
$R_8 = "A8"
$R_9 = "A9"
$R_10 = "A10"
$R_11 = "A11"
$R_12 = "A12"
$R_13 = "A13"
$R_14 = "A14"
$R_15 = "A15"
$R_16 = "A16"
$R_17 = "A17"
$R_18 = "A18"
$R_19 = "A19"
$R_20 = "A20"
$R_21 = "A21"
$R_22 = "A22"
$R_23 = "A23"
$R_24 = "A24"
$store = "C:\Users\"
$FilePath
$input_file = $FilePath
$ini_file = #{}
Get-Content $input_file | ForEach-Object {
$_.Trim()
} | Where-Object {
$_ -notmatch '^(;|$)'
} | ForEach-Object {
if ($_ -match '^\[.*\]$') {
$section = $_ -replace '\[|\]'
$ini_file[$section] = #{}
} else {
$key, $value = $_ -split '\s*=\s*', 2
$ini_file[$section][$key] = $value
}
}
#--------
$Path_Store = $store
#---------
$Get_1 = $ini_file.($section).($R_1)
$L_1 = $Get_1.Substring(0,3)
$Get_2 = $ini_file.($section).($R_2)
$L_2 = $Get_2.Substring(0,3)
$Get_3 = $ini_file.($section).($R_3)
$L_3 = $Get_3.Substring(0,3)
#---------
$Outer = ";********************"
$Header = ";*******************"
$ML = "12345"
$FB = ";Initial=1a2b"
#----------
$B_ID_1 = ";Build=" + $ML + "#" + "S" + $L_1 + "#" + "D" + $L_1
$CRM_1 = ";CRM=" + $R_1
$Output_1 = $Header, $B_ID_1, $FB, $CRM_1 , $Outer | Out-File $Path_Store\A1
$B_ID_2 = ";Build=" + $ML + "#" + "S" + $L_2 + "#" + "D" + $L_2
$CRM_2 = ";CRM=" + $R_2
$Output_2 = $Header, $B_ID_2, $FB, $CRM_2 , $Outer | Out-File $Path_Store\A2
$B_ID_3 = ";Build=" + $ML + "#" + "S" + $L_3 + "#" + "D" + $L_3
$CRM_3 = ";CRM=" + $R_3
$Output_3 = $Header, $B_ID_3, $FB, $CRM_3 , $Outer | Out-File $Path_Store\A3
#---------
}
$call = F_ML
My expectation, I can make this way shorter and the output is getting 24 output file.
Output Sample
Output File 1
;********************
;Build=12345#S12#D12
;Initial=1a2b
;CRM=A1
;********************
Output File 2
;********************
;Build=12345#S23#D23
;Initial=1a2b
;CRM=A2
;********************
Try below...
$IniContent = Get-Content -Path $IninPath
$IniContent | ForEach-Object -Process {
$Split = $_ -split '=';
$OutPutfilePath = "c:\temp\$($Split[0]).txt"
$first = ($Split[1] -split ',')[0]
$append = "#S{0}D{1}" -f $first,$first
# create a herestring to build the output
#"
;********************
;Build=$Ml$append
$FB
;CRM=$($Split[0])
;********************
"# | Out-File -Path $OutPutfilePath -Force
}
Use a ForEach to manipulate a string and Invoke the Expression.
1..24 | ForEach-Object { Invoke-Expression -Command (
'$R_{0} = "A{0}"' -f $_
)}
.
.
.
1..24 | ForEach-Object { Invoke-Expression -Command (
'$Get_{0} = $ini_file.($section).($R_{0}) `
$L_{0} = $Get_{0}.Substring(0,3)' -f $_
)}
.
.
.
1..24 | ForEach-Object { Invoke-Expression -Command (
'$B_ID_{0} = ";Build=" + $ML + "#" + "S" + $L_{0} + "#" + "D" + $L_{0}
$CRM_{0} = ";CRM=" + $R_{0}
$Output_{0} = $Header, $B_ID_{0}, $FB, $CRM_{0} , $Outer | Out-File $Path_Store\A{0}' -f $_
)}
{0} will be replaced by the value behind -f so it will be replaced Foreach number from 1 to 24...
Would this work?
All I did here was use your existing code to run everything in a loop from 1 to 24, removing the duplicated code. I reformatted it a little so it was a little easier for me to read.
Essentially, the variable $i will be a number from 1 to 24, while the variable $R will be "A" and whatever number is in $i (essentially A + $i)
Function F_ML
{
$section = "Code"
$store = "C:\Users\"
$input_file = "C:\Users\File.ini"
$ini_file = #{}
for ($i=1; $i -le 24; $i++)
{
$R = "A$($i)"
Get-Content $input_file |
ForEach-Object `
{
$_.Trim()
} |
Where-Object `
{
$_ -notmatch '^(;|$)'
} |
ForEach-Object `
{
if ($_ -match '^\[.*\]$')
{
$section = $_ -replace '\[|\]'
$ini_file[$section] = #{}
}
else
{
$key, $value = $_ -split '\s*=\s*', 2
$ini_file[$section][$key] = $value
}
}
#--------
$Path_Store = $store
#---------
$Get = $ini_file.($section).($R)
$L = $Get.Substring(0,3)
#---------
$Outer = ";********************"
$Header = ";*******************"
$ML = "12345"
$FB = ";Initial=1a2b"
#----------
$B_ID = ";Build=" + $ML + "#" + "S" + $L + "#" + "D" + $L
$CRM = ";CRM=" + $R
$Output = $Header, $B_ID, $FB, $CRM , $Outer | Out-File $Path_Store\$R
}
}
So currently, I know you can grab an account using the ldap filter for something like $adSearchFilter = "(&(objectCategory=User)(samAccountType:1.2.840.113556.1.4.803:=805306368)(SamAccountName='Account1')). Is there a way the ldap filter will allow you to pass through a list of names, like instead of using = I can use something like -contains?
Below is the code, and as you can see, it searches one user at a time for the whole search process in a foreach loop...
Function GetUsersInfoFromDomain
{
Param ([String]$searchPropertyName, [String[]]$searchPropertyValues, [String[]]$DcWithCred,[String]$domainShortName, [String[]]$userProperties)
$queryTable = #()
ForEach ($searchPropertyValue in $searchPropertyValues)
{
$adSearchFilter = "(&(objectCategory=User)(samAccountType:1.2.840.113556.1.4.803:=805306368)($searchPropertyName=$searchPropertyValue))"
Write-Host "Searching domain $domainShortName with $searchPropertyName $searchPropertyValue"
$searchDomainResultsTable = powershell -command {
Param ([String]$adSearchFilter, [String[]]$userProperties,[String[]]$DcWithCred, [String]$domainShortName)
[string]$DC = $DcWithCred[0]
[string]$Username = $DcWithCred[1]
[string]$Password = $DcWithCred[2]
[string]$domain = "LDAP://$DC"
$adDomain = New-Object System.DirectoryServices.DirectoryEntry($domain, $Username, $Password)
$adSearcher = New-Object System.DirectoryServices.DirectorySearcher($adDomain)
$adSearcher.Filter = $adSearchFilter
$adSearcher.PageSize=1000
$adSearcher.PropertiesToLoad.AddRange($userProperties) | out-Null
$userRecords = $adSearcher.FindAll()
$adSearcher.Dispose() | Out-Null
[System.GC]::Collect() | Out-Null
# The AD results are converted to an array of hashtables.
$userPropertiesTable = #()
foreach($record in $userRecords) {
$hashUserProperty = #{}
foreach($userProperty in $userProperties){
if (($userProperty -eq 'objectGUID') -or ($userProperty -eq 'objectSid') -or ($userProperty -eq 'msExchMasterAccountSid')) {
if ($record.Properties[$userProperty]) {
$hashUserProperty.$userProperty = $record.Properties[$userProperty][0]
} else {
$hashUserProperty.$userProperty = $null
}
} Else {
if ($record.Properties[$userProperty]) {
$hashUserProperty.$userProperty = ($record.Properties[$userProperty] -join '; ').trim('; ')
} else {
$hashUserProperty.$userProperty = $null
}
} #end Else
} #end ForEach
$userPropertiesTable += New-Object PSObject -Property $hashUserProperty
} #end ForEach
[System.GC]::Collect() | Out-Null
# Fixes the property values to be a readable format before exporting to csv file
$listOfBadDateValues = '9223372036854775807', '9223372036854770000', '0'
$maxDateValue = '12/31/1600 5:00 PM'
$valuesToFix = #('lastLogonTimestamp', 'AccountExpires', 'LastLogon', 'pwdLastSet', 'objectGUID', 'objectSid', 'msExchMasterAccountSid')
$extraPropertyValues = #('Domain Name')
$valuesToFixCounter = 0
$extraPropertyValuesCounter = 0
$valuesToFixFound = #($false, $false, $false, $false, $false, $false, $false)
$extraPropertyValuesFound = #($false)
ForEach ($valueToFix in $valuesToFix)
{
if ($userProperties -contains $valueToFix)
{
$valuesToFixFound[$valuesToFixCounter] = $true
}
$valuesToFixCounter++
}
ForEach ($extraPropertyValue in $extraPropertyValues)
{
if ($userProperties -contains $extraPropertyValue)
{
$extraPropertyValuesFound[$extraPropertyValuesCounter] = $true
}
$extraPropertyValuesCounter++
}
$tableFixedValues = $userPropertiesTable | % {
if ($valuesToFixFound[0]) {
if ($_.lastLogonTimestamp) {
$_.lastLogonTimestamp = ([datetime]::FromFileTime($_.lastLogonTimestamp)).ToString('g')
}
}; if ($valuesToFixFound[1]) {
if (($_.AccountExpires) -and ($listOfBadDateValues -contains $_.AccountExpires)) {
$_.AccountExpires = ""
} else {
if (([datetime]::FromFileTime($_.AccountExpires)).ToString('g') -eq $maxDateValue) {
$_.AccountExpires = ""
} Else {
$_.AccountExpires = ([datetime]::FromFileTime($_.AccountExpires)).ToString('g')
}
}
}; if ($valuesToFixFound[2]) {
if (($_.LastLogon) -and ($listOfBadDateValues -contains $_.LastLogon)) {
$_.LastLogon = ""
} else {
if (([datetime]::FromFileTime($_.LastLogon)).ToString('g') -eq $maxDateValue) {
$_.LastLogon = ""
} Else {
$_.LastLogon = ([datetime]::FromFileTime($_.LastLogon)).ToString('g')
}
}
}; if ($valuesToFixFound[3]) {
if (($_.pwdLastSet) -and ($listOfBadDateValues -contains $_.pwdLastSet)) {
$_.pwdLastSet = ""
} else {
if (([datetime]::FromFileTime($_.pwdLastSet)).ToString('g') -eq $maxDateValue) {
$_.pwdLastSet = ""
} Else {
$_.pwdLastSet = ([datetime]::FromFileTime($_.pwdLastSet)).ToString('g')
}
}
}; if ($valuesToFixFound[4]) {
if ($_.objectGUID) {
$_.objectGUID = ([guid]$_.objectGUID).Guid
} Else {
$_.objectGUID = ""
}
}; if ($valuesToFixFound[5]) {
if ($_.objectSid) {
$_.objectSid = (New-Object Security.Principal.SecurityIdentifier($_.objectSid, 0)).Value
} Else {
$_.objectSid = ""
}
}; if ($valuesToFixFound[6]) {
if ($_.msExchMasterAccountSid) {
$_.msExchMasterAccountSid = (New-Object Security.Principal.SecurityIdentifier($_.msExchMasterAccountSid, 0)).Value
} Else {
$_.msExchMasterAccountSid = ""
}
}; If ($extraPropertyValuesFound[0]) {
If (!($_.'Domain Name')) {
$_.'Domain Name' = $domainShortName
}
};$_}
[System.GC]::Collect() | Out-Null
$sortedTableColumns = $tableFixedValues | Select-Object $userProperties
[System.GC]::Collect() | Out-Null
return $sortedTableColumns
} -args $adSearchFilter, $userProperties, $DcWithCred, $domainShortName
[System.GC]::Collect() | Out-Null
Write-Host "Search Complete."
Write-Host ""
if ($searchDomainResultsTable)
{
$queryTable += $searchDomainResultsTable
}
} # End ForEach Loop
Write-Host 'Exporting domain search results to table...'
Write-Output $queryTable
}
I thought about doing something like $adSearchFilter += "($searchPropertyName=$searchPropertyValue)". However due to the 10mb limit - What is the LDAP filter string length limit in Active Directory?, I'm not sure if this would be the best method while looking up 200,000++ users.
Does anyone know a way to pass a list instead of 1 string value per search?
LDAP doesn't have a -contains-like statement, but you can use the OR operator (|) to construct a filter expression that matches multiple exact values:
(|(samaccountname=user1)(samaccountname=user2)(samaccountname=user3))
This is how I would build the filter string:
$FilterTemplate = '(&(objectCategory=User)(samAccountType:1.2.840.113556.1.4.803:=805306368){0})'
$ClauseTemplate = "($searchPropertyName={0})"
$AllClauses = $searchPropertyValues |ForEach-Object { $ClauseTemplate -f $_ }
$adSearchFilter = $FilterTemplate -f $($AllClauses -join '')
That being said, why would you pass 200000 specific values to search for in a single search? LDAP supports wildcard matching (eg. (samaccountname=*)).
In any case, you could calculate the final size of your string, by calling Encoding.GetByteCount on the biggest string in $AllClauses, and then use that to partition the array (let's cap it at 9.5 MB to be on the safe side):
$LongestString = $AllClauses |Sort -Property Length |Select -Last 1
$LongestByteCount = [System.Text.Encoding]::Unicode.GetByteCount($LongestString)
if(($LongestByteCount * $AllClauses.Count) -gt 9.5MB)
{
$MaxCount = [int](9.5MB / $LongestByteCount)
for($i = 0; $i -lt $AllClauses.Count; $i += $MaxCount)
{
$ClauseSubset = $AllClauses[$i..$($i + $MaxCount - 1)]
$adSearchFilter = $FilterTemplate -f $($ClauseSubset -join '')
# Do your search
}
}
EDITED:
Text1.txt:
123.456.789.189:12345
222.222.222.444:56789
451.200.111.321:55555
333.333.333.111:11223
I want to compare ID with IP that weren't registered.
ERROR:
Exception calling "Add" with "2" argument(s): "Item has already been added. Key in dictionary: '123.456.789.189:12345' Key being added: '123.456.789.189:12345'" + $nameHash.Add( $data3[4], $data3[3] )
I think this error is due to the existence of duplicates.
How do I solve an issue with duplicates in Hash Table?
My function to calculate time takes in a startdate and an end date.
Function calTimeDiff( $StartDate, $EndDate )
{
"which is = " + (NEW-TIMESPAN –Start $StartDate –End $EndDate).Hours + " hours, " +
(NEW-TIMESPAN –Start $StartDate –End $EndDate).Minutes + " minutes, " +
(NEW-TIMESPAN –Start $StartDate –End $EndDate).Seconds + " seconds, " +
"diff = " + (NEW-TIMESPAN –Start $StartDate –End $EndDate).TotalSeconds + " sec"
}
$lines1 = Get-Content "C:\Temp\Text1.txt" | Select-Object -Unique
$lines2 = Get-Content "C:\Temp\Text2.txt" | Select-Object -Unique
ForEach( $line2 in $lines2 )
{
$list = ( $date, $time, $client, $clientIP )
$list = $line2.Split( "" )
ForEach( $line1 in $lines1 )
{
$disconnectIP = $line1
If( $disconnectIP -match $list[3] )
{
$date = $list[0]
$time = $list[1]
$client = $list[2]
$clientIP = $list[3]
If( $client -eq "serviceClient" )
{
$start = $date + " " + $time
}
If( $client -eq "Unregistered" )
{
$end = $date + " " + $time
}
calTimeDiff $start $end
}
}
}
How about something along these lines? I think it's basically behaving the way you were asking for (although you might want to tweak the display-span function a bit...)
#requires -Version 3
function parse-log
{
param(
[string]$line
)
$data = $line.split(' ')
$dateString = '{0} {1}' -f $data[0], $data[1]
$timeStamp = Get-Date -Date $dateString
[pscustomobject]#{
TimeStamp = $timeStamp
Client = $data[2]
IPAddress = $data[3]
}
}
function display-span
{
param(
$logSpan
)
'{0} ({1}) ==> {2}' -f $logSpan.IPAddress, $nameHash.Get_Item( $logSpan.IPAddress), $logSpan.Start
'{0} ({1}) ==> {2}' -f $logSpan.IPAddress, $nameHash.Get_Item( $logSpan.IPAddress), $logSpan.End
'Start = {0}, End = {1}, diff = {2}' -f $logSpan.Start, $logSpan.End, $logSpan.TimeSpan
''
}
$ipStateHash = #{}
$nameHash = #{}
$logArray = #()
$lines1 = Get-Content -Path '.\Text1.txt'
$lines2 = Get-Content -Path '.\Text2.txt'
$lines3 = Get-Content -Path '.\Text3.txt'
# Build Name Hash
foreach( $line3 in $lines3 )
{
$data3 = $line3.Split( ' ' )
$nameHash.Add( $data3[4], $data3[3] )
}
foreach( $line2 in $lines2 )
{
$entry = parse-log -line $line2
switch( $entry.Client ) {
'serviceClient'
{
if( $lines1 -contains $entry.IPAddress )
{
if( $ipStateHash.ContainsKey( $entry.IPAddress ) -eq $false )
{
$ipStateHash.Add( $entry.IPAddress, $entry.TimeStamp )
}
}
}
'Unregistered'
{
if( $ipStateHash.ContainsKey( $entry.IPAddress ) -eq $true )
{
$start = $ipStateHash.Get_Item( $entry.IPAddress )
$ipStateHash.Remove( $entry.IPAddress )
$timespan = $entry.TimeStamp - $start
$logArray += [pscustomobject]#{
IPAddress = $entry.IPAddress
Start = $start
End = $entry.TimeStamp
TimeSpan = $timespan
}
}
}
}
}
$logArray | ForEach-Object -Process {
display-span -logSpan $_
}
"IPs that weren't Unregistered:"
$ipStateHash.GetEnumerator() | Sort-Object -Property TimeStamp | ForEach-Object -Process {
'{0} ==> {1}' -f $nameHash.Get_Item( $_.Key ), $_.Value
}
Using your updated data files from above, the script outputs:
123.456.789.189:12345 (BOB) ==> 7/29/2015 6:00:13 AM
123.456.789.189:12345 (BOB) ==> 7/29/2015 6:00:19 AM
Start = 7/29/2015 6:00:13 AM, End = 7/29/2015 6:00:19 AM, diff = 00:00:06
222.222.222.444:56789 (ALICE) ==> 7/29/2015 6:00:18 AM
222.222.222.444:56789 (ALICE) ==> 7/29/2015 6:00:22 AM
Start = 7/29/2015 6:00:18 AM, End = 7/29/2015 6:00:22 AM, diff = 00:00:04
451.200.111.321:55555 (TOM) ==> 7/29/2015 6:20:03 AM
451.200.111.321:55555 (TOM) ==> 7/29/2015 6:21:19 AM
Start = 7/29/2015 6:20:03 AM, End = 7/29/2015 6:21:19 AM, diff = 00:01:16
IPs that weren't Unregistered:
BOB ==> 7/29/2015 6:01:00 AM
I have a powershell script that works perfectly when I manually execute it from the command prompt using:
powershell -NoLogo -NonInteractive -File "D:\ServerFolders\Company\Scripts\Powershell Scripts\SendMonthlyGrowthRateReport.ps1"
The script is opening an excel spreadsheet, pulling some data from SQL into the spreadsheet, saves a copy of the spreadsheet with the current Date on it, and then emails the spreadsheet out via SQL sp_send_dbmail. It also logs current status to a log file.
Now I setup a scheduled task on Windows 2012 R2 to execute the above command once a month, but when I manually run the task or try to let it kick itself off, only the log file is generated, the spreadsheet isn't saved and email never goes out.
Any Ideas?
UPDATE:
below is the code in the script, I should also note that powershell returns exit code 0 when executed through the scheduled task...
$DebugPreference = 2
$VerbosePreference = 2
$WarningPreference = 2
#param([string] $TemplateFilePath = "TemplateFilePath",
# [string]$StorageRoot = "StorageRoot",
# [string]$NameFormat = "NameFormat",
# [string]$ReportType = "ReportType",
# [string] $SendReportTo = "SendReportTo")
$TemplateFilePath = 'D:\ServerFolders\Company\Spreadsheets\templates\DatabaseGrowthTemplate.xlsx'
$StorageRoot = 'D:\ServerFolders\Company\Spreadsheets'
$NameFormat = '%type% Database Growth Report %date%.xlsx'
$ReportType = "Monthly"
$SendReportTo ="*******#someDomain.com"
$Date = get-Date
$LogFile = "D:\ServerFolders\Company\SyncLogs\MonthlyGrowthReport" + $Date.toString("yyyy-MM-dd hhmmss") + ".log"
Function LogWrite
{
Param ([string]$logstring)
$date = get-date
$DateString = $date.toString("yyyy-MM-dd hh:mm:ss")
$FileValue = $DateString +' - ' + $logstring
if($LogFile -ne $null -and $LogFile -ne "")
{
Add-content $Logfile -value $FileValue
}
Write-Host $FileValue
}
if ($ReportType -ne 'Weekly' -and $ReportType -ne 'Monthly' -and $ReportType -ne 'Quarterly' -and $ReportType -ne 'Yearly')
{
Write-Host "Valid options for ReportType parameter are 'Weekly', 'Monthly', 'Quarterly', or 'Yearly'"
exit
}
$Date = get-Date
$DateString = $Date.ToString("yyyy-MM-dd")
$FromDate = $DateString
$FileName = $NameFormat.Replace("%date%", $DateString)
$FileName = $FileName.Replace("%type%", $ReportType)
$Destination = join-path $StorageRoot $FileName
$LogWrite = "Generate Destination File of " + $Destination
LogWrite $LogWrite
$IncrementType ="Days"
if ($ReportType -eq "Weekly"){
$IncrementType = "Weeks"
}
if ($ReportType -eq "Monthly"){
$IncrementType = "Months"
}
if ($ReportType -eq "Quarterly"){
$IncrementType = "Quarters"
}
if ($ReportType -eq "Yearly") {
$IncrementType = "Years"
}
$IncrementBackValue = -1
## Connect to the target SQL Server and run the query to refresh data
$cstr = "Server=SERVERNAME\INSTANCENAME;Database=Logging;Trusted_Connection=True;"
$cn = new-object system.data.SqlClient.SqlConnection($cstr);
LogWrite "Connecting to SQL"
$cn.Open()
#Query the first date from using the built-in function
$dateQuery = "SELECT [dbo].[ufn_getIncrementBackDate]('$DateString', '${IncrementType}', ${IncrementBackValue}) as [StartDate]"
$cmd1 = New-Object System.Data.SqlClient.SqlCommand($dateQuery, $cn)
$DateDS = $cmd1.ExecuteReader();
while($DateDS.Read()){
$StartDate = $DateDS.GetDateTime(0)
}
$DateDS.Close()
#Copy isn't needed Open the template and then at the End do a save as
LogWrite "Opening Excel workbook..."
# Open the Excel document and pull in the 'Data' worksheet
$Excel = New-Object -Com Excel.Application
$Workbook = $Excel.Workbooks.Open($TemplateFilePath)
$page = 'Data'
$ws = $Workbook.worksheets | where-object {$_.Name -eq $page}
# Delete the current contents of the page
$ws.Cells.Clear() | Out-Null
LogWrite "Generating Report Data..."
#Prepare adapter objects for reading info into Excel
$ds = new-object "System.Data.DataSet" "dsProductData"
$q = "usp_GenerateDatabaseSizeReport #FirstDate='$StartDate'"
$da = new-object "System.Data.SqlClient.SqlDataAdapter" ($q, $cn)
$da.Fill($ds) | Out-Null
$dtProduct = $ds.Tables[0]
# Set variables for the worksheet cells, and for navigation
$cells=$ws.Cells
$row=1
$col=1
$MaxCol = 1
$MaxRow = 1
LogWrite "Populating Data worksheet.."
#Fill Headers
foreach($column in $dtProduct.Columns){
$cells.item($row, $col) = $column.ColumnName
if ($col -gt $MaxCol){
$MaxCol = $col
}
$col++
}
# Add the results from the DataTable object to the worksheet
foreach($dataRow in $dtProduct){
$row++
$col = 1
foreach($column in $dtProduct.Columns)
{
if ($col -eq 1){
$cells.item($row, $col) = $dataRow[$column.ColumnName].ToString()
} else {
$cells.item($row, $col) = $dataRow[$column.ColumnName].ToString()
}
$col++
}
if ($row -gt $MaxRow){
$MaxRow = $row
}
}
LogWrite "Finished populating Data..."
# Set the width of the columns automatically
$ws.columns.item("A:Z").EntireColumn.AutoFit() | out-null
#Format Date Column
$ws.Range("A2:A1000").NumberFormat ="m/d/yyyy"
#Create the Line Chart's
$ColumnLetter = "A"
if ($MaxCol -eq 1) { $ColumnLetter = "A" }
if ($MaxCol -eq 2) { $ColumnLetter = "B" }
if ($MaxCol -eq 3) { $ColumnLetter = "C" }
if ($MaxCol -eq 4) { $ColumnLetter = "D" }
if ($MaxCol -eq 5) { $ColumnLetter = "E" }
if ($MaxCol -eq 6) { $ColumnLetter = "F" }
if ($MaxCol -eq 7) { $ColumnLetter = "G" }
if ($MaxCol -eq 8) { $ColumnLetter = "H" }
if ($MaxCol -eq 9) { $ColumnLetter = "I" }
if ($MaxCol -eq 10) { $ColumnLetter = "J" }
if ($MaxCol -eq 11) { $ColumnLetter = "K" }
if ($MaxCol -eq 12) { $ColumnLetter = "L" }
if ($MaxCol -eq 13) { $ColumnLetter = "M" }
if ($MaxCol -eq 14) { $ColumnLetter = "N" }
if ($MaxCol -eq 15) { $ColumnLetter = "O" }
if ($MaxCol -eq 16) { $ColumnLetter = "P" }
if ($MaxCol -eq 17) { $ColumnLetter = "Q" }
if ($MaxCol -eq 18) { $ColumnLetter = "R" }
if ($MaxCol -eq 19) { $ColumnLetter = "S" }
if ($MaxCol -eq 20) { $ColumnLetter = "T" }
if ($MaxCol -eq 21) { $ColumnLetter = "U" }
if ($MaxCol -eq 22) { $ColumnLetter = "V" }
if ($MaxCol -eq 23) { $ColumnLetter = "W" }
if ($MaxCol -eq 24) { $ColumnLetter = "X" }
if ($MaxCol -eq 25) { $ColumnLetter = "Y" }
if ($MaxCol -eq 26) { $ColumnLetter = "Z" }
$RangeString = "A1:"+$ColumnLetter
#$RangeString
$RangeString =$RangeString + $MaxRow
#$RangeString
$range = $ws.range($RangeString)
LogWrite "Performing Chart updates."
$page = "Chart"
$ws = $Workbook.worksheets | where-object {$_.Name -eq $page}
foreach($Shape in $ws.Shapes){
if ($Shape.HasChart){
$chart = $Shape.Chart
break
}
if ($chart -ieq $null){
Write-Host "Can't find chart!!!"
} else {
$chart.SetSourceData($range)
}
LogWrite "Saving updated copy of Excel workbook."
# Close the workbook and exit Excel
$Workbook.SaveAs($Destination)
$workbook.Close($true)
$excel.quit()
$DestinationFileO = New-Object System.IO.FileInfo($Destination)
$EmailSubject = $DestinationFileO.Name.Replace($DestinationFileO.Extension, "")
LogWrite "Sending Excel Workbook via email."
#Send an email to operator with report
$SendEmailCmdText ="exec msdb..sp_send_dbmail #profile_name='GMail'
, #recipients = '${SendReportTo}'
, #subject = '${EmailSubject}'
, #body = 'Attached is the $EmailSubject for database server [HOMEGROWNSERVER\TFSSQL].'
, #file_attachments = '${Destination}'
--, #query_result_header = 1
--, #query_result_separator=','
--, #query_result_width = 32767
--, #append_query_error = 1
--, #query_result_no_padding = 1"
$cmd2 = New-Object System.Data.SqlClient.SqlCommand($SendEmailCmdText, $cn)
$cmd2.ExecuteNonQuery()
$cn.Close()
LogWrite "Process Complete"
Your most likely problem is that opening Excel requires an interactive session.
$Excel = New-Object -Com Excel.Application
$Workbook = $Excel.Workbooks.Open($TemplateFilePath)
These lines will fail when you run the script as a scheduled job. As far as I know; in Windows 2012 R2; the only way to get this to work, is to set the scheduled job to “Run only when the user is logged on” and leave the user logged on.
Sorry about the non-answer, but I have not found a workaround for this yet.