Search Variable for String then Run Conditional Statement in PowerShell - sql-server-2008

I am pretty new with PowerShell. I was recently tasked with making a error message popup that would help a local user determine whether or not a MS SQL on-demand DB merge worked or not. I wrote a script that woudld do the following:
Run the batch file that conducted the merge
Read the results of a text log file into a variable
Check the variable for any instances of the word "ERROR" and pop a success or fail dialog box depending on whether or not it found the word error in the log file.
Quick and simple I thought but I appear to be struggling with the conditional statement. Here is the script:
cmd /c c:\users\PERSON\desktop\merge.bat
$c = get-content c:\replmerg.log
if ($c -contains ("ERROR"))
{
$a = new-object -comobject wscript.shell
$b = $a.popup(“ERROR - Database Merge“,0,”Please Contact Support”,1)
}
else
{
$a = new-object -comobject wscript.shell
$b = $a.popup(“SUCCESS - Database Merge“,0,”Good Job!”,1)
}
Right now what happens is that the script runs and just skips to the Success message. I can confirm that simply running the 'get-content' command in powershell will on its own produce a variable that I can then call and show the content of the log file. My script however does not appear as though it is actually checking the $c variable for the word and then popping the error message as intended. What am I missing here?

Christian's answer is correct. You could also use the -match operator. For example:
if ((Get-Content c:\replmerg.log) -match "ERROR")
{
'do error stuff'
}
else
{
'do success stuff'
}
You can use -cmatch if you want a case sensitive comparison.

You actually don't need to use Get-Content at all. Select-String can take a -path parameter. I created two very simple text files, one which has the word ERROR and one which does not
PS C:\> cat .\noerror.txt
not in here
PS C:\> cat .\haserror.txt
ERROR in here
this has ERROR in here
PS C:\> if ( Select-String -Path .\noerror.txt -Pattern ERROR) {"Has Error"}
PS C:\> if ( Select-String -Path .\haserror.txt -Pattern ERROR) {"Has Error"}
Has Error
PS C:\>
The one thing that might trip you up is that the -pattern actually takes a regular expression, so be careful of what you use for your pattern. THis will find ERROR anywhere in the log file, even if there are multiple instances, like in my "haserror.txt" file.

The -contains operator is used for looking for an exact match in a list (or array). As the other answers indicate, you should use -match, -like, or -eq to compare strings.

You can use the -quiet switch of select-string if you just wnat to test for the presence of a string in a file.
select-string -path c:\replmerg.log -pattern "ERROR" -casesensetive -quiet
Will return $true if the string is found in the file, and $false if it is not.

contain operator test only an identical value (not part of a value).
You can try this
$c = get-content c:\replmerg.log | select-string "ERROR" -casesensitive
if ($c.length -gt 0)

Related

ConvertFrom-Json : Invalid object passed in, ':' or '}' expected

Earlier this week, I asked this question to find the best way to go through a directory of text files with log information in JSON format and count how many of each unique messages there are.
I was able to do so with the answers provided. However, the problem I'm having now is that one of the files is formatted in a way that ConvertFrom-JSON doesn't like. It throws the error:
ConvertFrom-Json : Invalid object passed in, ':' or '}' expected.
Initially, I thought I could use 'erroraction -silentlycontinue' to skip that file and move on (there's just one line with nothing meaningful in it). However, it appears to be a known issue that this doesn't work with ConvertFrom-JSON and the alternative is to use a Try / Catch.
How would I use a try / catch to bypass the one bad file? Or is there another way to cleanly skip this file without having to remove it from the directory?
Here is what I have started out with. It's not much, but some guidance on this would be great. I have also seen some info online that I would use gc -Raw or Out-string before the ConvertFrom-JSON, but that gave me the same result.
try {
gci -Path "path" | gc | ConvertFrom-Json | Group-Object message -NoElement
}
catch {
write-host "can't convert file to JSON"
}
finally {
}
You'll have to insert your error handling into the pipeline. This is where ForEach-Object will come in handy:
Get-ChildItem -Path "path" |
ForEach-Object -Process {
try {
$_ | Get-Content | ConvertFrom-Json
} catch {
write-host "can't convert file '$_' to JSON"
}
} | Group-Object message -NoElement
The successful conversions will be passed through.
Additionally, consider using Write-Warning instead of Write-Host for your error condition. It seems to best fit this situation, and can be redirected or opted out by an invoker.
If you think this condition is even less serious than a warning, consider Write-Verbose so invokers can opt in instead.
The answer worked for me
ConvertFrom-Json cannot read my JSON
Use the -Raw parameter of the Get-Content cmdlet, otherwise Get-Content reads each line separately and it will be stored in the variable as an array.
$json = Get-Content c:\temp\net\cars.json -Raw
ConvertFrom-Json $json
Try this and you will end up with a nicely usable object:
$cars = Get-Content c:\temp\net\cars.json -Raw | ConvertFrom-Json | Select -ExpandProperty cars

Passing a [ref] parameter in a remote session in Powershell

I have a Powershell question.
I am trying to get a value from a function to a variable, by calling a function with a reference to the variable.
For example:
$var = New.Object System.Object;
Example-Function -OutObject ([ref]$var);
Where the Example-Function is defined like this:
function Example-Function
{
[CmdletBinding()]
param
(
[Parameter(Mandatory=$true)]
[ref]
$OutObject
)
$SomeValue = ...
#Write some output
#Do something...
$OutObject.Value = $SomeValue;
}
This is working OK. The $var variable gets it's value from the function ($SomeValue).
But, this is not working when the Example-Function is imported into remote session, for example:
$creds = New-Object System.Management.Automation.PSCredential('user','pass')
$session = New-PSSession -ComputerName 'ExampleComputer' -Credential $creds -Authentication CredSSP
Import-PSSession -Session $session -CommandName 'Example-Function' -AllowClobber
$var = New.Object System.Object;
Example-Function -OutObject ([ref]$var);
This code is throwing the following error: Cannot process argument transformation on parameter 'OutObject'. Reference type is expected in argument.
I am assuming that this is becuase the Example-Function is now running on the other computer ('ExampleComputer'), while ([ref]$var) is referencing the variable in memory of the computer running the scripts (my computer).
The reason I don't want to (cannot) use the return statement way is becuase my function is writing some output, and in Powershell, everything that is outputed from a function is returned.
So, my question is, can I get a value from a function that has a lot of output into the variable, when the function is running in the remote session?
If it cannot be done by using the [ref] parameter, is there another way?
Thanks
Okay lets try again:
Invoke-Command returns whatever is run in the remote pipeline. Which means you can do:
$var = Invoke-Command -session $session -command {Example-Function}
Which saves everything in the $var variable. You can then filter the results and get whatever information you need.
And please remember [ref] just makes everything more complicated than it actually is.

Powershell Copy Directories - Using A CSV Listing - Source - Destination Path

I am trying to Copy Directories - folders and Sub folders to another location using a CSV file that lists the source and destination of each directory or folder to be copied.
The Contents of the CSV are as such below:
I have referenced this thread:
https://serverfault.com/questions/399325/copying-list-of-files-through-powershell
Import-CSV C:\Users\WP\Desktop\a.csv | foreach{Copy-item "$_.Source" "$_.Destination"}
Error Received
CategoryInfo : ObjectNotFound: (#{Source=C:String) [Copy-Item], DriveNotFoundException
+ FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.CopyItemCommand
The other question I have is if in the CSV I want to copy to a folder that does not exists in the destination - can I use the CSV to command powershell to create the folder?
Thank you for your advice.
PowerShell will not expand the variable and access the property of the object inside the variable if you have them placed in double quotes by default. Only the '$_' is being expanded and '.source' is being tacked on to the end of the string, so from the view of the shell, your command looks something like Copy-item "{source=C:\Users\WP\Desktop\a;Destination=C:\Users\WP\Desktop\a}.Source" "{source=C:\Users\WP\Desktop\a;Destination=C:\Users\WP\Desktop\a}.Destination", which is probably not what you mean.
Here is the syntax that should work (I also included -Recurse so that it will copy the items inside the directory as well)
Import-CSV C:\Users\WP\Desktop\a.csv | foreach{Copy-item -Path $_.Source -Destination $_.Destination -Recurse}
Note: if you want to access the properties on an object inside of double quotes, use this syntax "$($_.source)".
For a csv like this:
Source,Destination
D:\junk\test1,D:\junk\test3
D:\junk\test2,D:\junk\test4
You can use code like the following:
$csv = Import-Csv D:\junk\test.csv
$csv | ForEach-Object {
if (-not (Test-Path $_.Destination)) {
New-Item -Name $_.Destination -ItemType Directory -Force -WhatIf
}
Copy-Item $_.Source $_.Destination -Recurse -Force -WhatIf
}
Suggestions for learning more about PowerShell:
Use WhatIf to test things.
Research what each line of this code does.
Experiment with code to see what it does.
Learn and use the debugger (PowerShell ISE) to help you write better code.
Remove the WhatIf parameters from the code to have it execute for real...
If you have dozens of problems that all involve doing the same thing with each element of a list, you might want to consider getting or writing a generic CSV template expander tool, like Expand-csv. With this tool you start with a CSV file and a template, and generate a script that contains all the commands.
Sample.csv looks like this:
Source,Destination
C:\Users\WP\Desktop\a,C:\Users\WP\Desktop\c
C:\Users\WP\Desktop\b,C:\Users\WP\Desktop\d
Sample.tmplt looks like this:
Copy-Item -Path $Source -Destination $Destination -Recurse
And the command to invoke Expand-csv looks like this:
Expand-csv Sample.csv Sample.tmplt > Sample.ps1
The output file, Sample.ps1 contains one copy command for each entry in the CSV file
And here is the definition of Expand-csv:
<# This function is a table driven template tool.
It's a refinement of an earlier attempt.
It generates output from a template and
a driver table. The template file contains plain
text and embedded variables. The driver table
(in a csv file) has one column for each variable,
and one row for each expansion to be generated.
5/13/2015
#>
function Expand-csv {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string] $driver,
[Parameter(Mandatory=$true)]
[string] $template
)
Process
{
$OFS = "`r`n"
$list = Import-Csv $driver
[string]$pattern = Get-Content $template
foreach ($item in $list) {
foreach ($key in $item.psobject.properties) {
Set-variable -name $key.name -value $key.value
}
$ExecutionContext.InvokeCommand.ExpandString($pattern)
}
}
}

Powershell: How to throw an error if a CSV entry is blank

I've written an extensive script that runs through an AD termination process, and the script can obtain the necessary information from a CSV. How do I make it so that it errors out if the entry is blank in the CSV? I've tried putting in Try-Catch, If-Else, everything that I know how to do. I've tried changing the error action, and I can get it to throw system generated errors (ex. "Cannot bind parameter "Identity" to the target..."), but I cannot get it to do what I want. Please see the code example below:
(Yes, I know that I'm duplicating values. This of importance later on in the script, and not the part I'm having issues with)
$owner = $user.'Network User ID'}
$loginID = $user.'Network User ID'
$Identity = Get-ADUser -Identity $owner -Properties Displayname |Select-Object -ExpandProperty Displayname
$manager = $user.'Provide Inbox Access To'
$NewOwner = $user.'Provide users email group ownership to'
$NewOwnerID = $User.'Provide users email group ownership To'
What I need it to do is throw an error if ANY entry in the CSV is blank, and terminate. The most promising idea that I tried was:
If ($Owner -eq $Null)
{
Write-Host "Invalid entry, the Network User ID field cannot be blank"
Write-Host "Press Enter to Exit..."
Exit
}
Else
{
#Do everything else
}
But even that still fails.
In summary, what I need to do is throw a custom terminating error if an entry in the CSV is blank.
Any help is greatly appreciated!
EDIT
If this helps, here is more of the real code...
$Confirmation = Read-Host "Please double check the information in the file. Are you sure you want to continue? (Y/N)"
If($Confirmation -eq "Y")
{
Write-Host "You have chosen to proceed. Processing Termination" -BackgroundColor DarkCyan
#Import file
$file = "C:\TerminateUsers.csv"
$data = Import-Csv $file
#Set disabled OU
$disabledOU = "OU=Users,OU=Disabled Accounts, OU=Corporate"
$colOutput = #()
foreach ($user in $data)
{
#Grab variables from CSV
$owner = $user.'Terminated Network User ID'}
$loginID = $user.'Terminated Network User ID'
#Displayname required for Outlook functions
$Identity = Get-ADUser -Identity $owner -Properties Displayname |Select-Object -ExpandProperty Displayname
$manager = $user.'Provide Inbox Access To'
$NewOwner = $user.'Provide users email group ownership to'
$NewOwnerID = $User.'Provide users email group ownership To'
If (Get-ADUser -LDAPFilter "(sAMAccountName=$loginID)")
{
$date = Get-Date -Format d
#Disable account, change description, disable dialin, remove group memberships
Set-ADUser -Identity $loginID -Enabled $false
Set-ADUser -Identity $loginID -Replace #{Description = "Terminated $date"}
Set-ADUser -Identity $loginID -Replace #{msNPAllowDialin = $False}
RemoveMemberships $loginID
This isn't all of it, but this is the part we're working with...
There's a number of issues you're going to run into here.
First, $Owner -eq $Null isn't going to do what you likely want to do. Mainly, the issue is that an empty string is not a null value. They're different. Instead, your test should be:
if ([string]::IsNullOrEmpty($owner)) { ... }
Or:
if ([string]::IsNullOrWhiteSpace($owner)) { ... }
This second one returns true if the string includes only tabs, spaces, or other whitespace characters, or is an empty string, or is null.
Second, to throw an exception, you need to use the throw keyword. See Get-Help about_Throw. For example:
if ([string]::IsNullOrWhiteSpace($owner)) {
throw "Owner is null or empty.";
}
If you have this embedded in a try block, you can catch the exception with the associated catch blocks. See Get-Help about_Try_Catch_Finally. You can also use Trap, I believe (See Get-Help about_Trap).
Finally, the default action when an error is encountered is controlled by the $ErrorActionPreference variable. That variable's default value is Continue, so error messages will be displayed but the script will continue executing as though no error happened at all. I'm not entirely sure how this works with manually thrown exceptions and try/catch blocks, but unless I know that I want my script to ignore errors, I start just about every script with:
$ErrorActionPreference = Stop;
See Get-Help about_Preference_Variables and Get-Help about_CommonParameters for more about this one.
Consider the following dataset. Note the null for Last_Name for one of the columns.
user_name first_name last_name
--------- ---------- ---------
lrivera0 Lawrence Rivera
tlawrence1 Theresa Lawrence
rboyd2 Roy
cperry3 Christine Perry
jmartin4 Jessica Martin
So if we want to be sure to only process full rows then a simple If would cover that.
Import-Csv .\text.csv | ForEach-Object{
If($_.Psobject.Properties.Value -contains ""){
# There is a null here somewhere
Throw "Null encountered. Stopping"
} else {
# process as normal
}
}
Problem is that Import-CSV treats nulls as zero length strings. I tried using -contains on just $_ but it did not work as $_ is not an array but an object with properties. So I used the object properties value to perform the comparison against.
Bacon brought up an interesting point in that this code would not account for whitespace only empty values.
We use throw so processing stops if a null is encountered. Using that if block you can do whatever action you want.

Extraneous data returned from Invoke-Command

I'm working with PowerShell to gather data from a list of remote servers which I then turn into a JSON object. Everything is working fine, but I get some really weird output that I can't seem to exclude.
I've tried piping the Invoke-Command results and excluding properties. I've also tried removing the items manually from the returned hash file, but I can't seem to make them go away.
What am I missing?
EDIT:
For the sake of figuring out what's wrong here is a simplified, but still broken, script:
$returnedServer = #{}
$pass = cat "C:\...\securestring.txt" | convertto-securestring
$mycred = new-object -typename System.Management.Automation.PSCredential -argumentlist "UserName",$pass
$s = #("xx.xxx.xxx.xxx","xx.xxx.xxx.xxx")
foreach($server in $s)
{
$returnedServer.$server += ,(Invoke-Command -ComputerName $server -ScriptBlock
{
1
}-credential $mycred | select -ExcludeProperty PSComputerName,RunSpaceID,PSShowComputerName)
$returnedServer| ConvertTo-Json
Which outputs:
{
"xx.xxx.xxx.xxx": [
{
"value": 1,
"PSComputerName": "xx.xxx.xxx.xxx",
"RunspaceId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"PSShowComputerName": xxxx
}
],
"xx.xxx.xxx.xxx": [
{
"value": 1,
"PSComputerName": "xx.xxx.xxx.xxx",
"RunspaceId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"",
"PSShowComputerName": xxxx
}
]
}
This post is really old, but I was unable to find an acceptable answer 6 years later, so I wrote my own.
$invokeCommandResults | ForEach-Object {
$_.PSObject.Properties.Remove('PSComputerName')
$_.PSObject.Properties.Remove('RunspaceId')
$_.PSObject.Properties.Remove('PSShowComputerName')
}
You need to use Select-Object to limit the result to just the properties you want to show up in the JSON output:
$returnedServers.$server += ,(Invoke-Command -ComputerName $server -ScriptBlock
{
...$serverHash = various look ups and calculations...
$serverHash
} | select PropertyA, PropertyB, ...)
For a more thorough answer you need to go into far more detail about your "various look ups and calculations" as well as the actual conversion to JSON.
After some testing, it seems the problem is the object type. I was able to get your test script to work by explicitly casting the returned result.
$returnedServer = #{}
$pass = cat "C:\...\securestring.txt" | convertto-securestring
$mycred = new-object -typename System.Management.Automation.PSCredential -argumentlist "UserName",$pass
$s = #("xx.xxx.xxx.xxx","xx.xxx.xxx.xxx")
foreach($server in $s)
{
$returnedServer.$server += ,[int](Invoke-Command -ComputerName $server -ScriptBlock {1} -credential $mycred)
}
$returnedServer| ConvertTo-Json
You could try this... instead of attempting to exclude extraneous property values, just be specific and "call" or "grab" the one(s) you want.
Quick Code Shortcut Tip! BTW, the Invoke-Command -Computer $server -Scriptbock {command} can be greatly simplified using: icm $server {command}
Now, getting back on track...
Using your original post/example, it appears that you are attempting to utilize one "value" by excluding all other values, i.e. -ExcludeProperty (which it is ultra-frustrating).
Let's start by removing and replacing the only exclusion section:
select -ExcludeProperty PSComputerName,RunSpaceID,PSShowComputerName
And instead, attempt to use one of the following:
1st Method: using the modified original command...
$returnedServer.$server += ,(Invoke-Command -ComputerName $server -ScriptBlock {1}-credential $mycred).value
2nd Method: using the "icm" version...
$returnedServer.$server += ,(icm $server {1} -credential $mycred).value
Essentially, you are "picking out" the value(s) you need (vs. excluding property values, which is, again, pretty frustrating when it does NOT work).
Related Example(s) follows:
Here is a typical system Powershell/WMIC command call:
icm ServerNameGoesHere {Get-CimInstance -ClassName win32_operatingsystem}
But what if I only want the "version" from the object glob:
(icm ServerNameGoesHere {Get-CimInstance -ClassName win32_operatingsystem}).version
But, hold on, now I only want the "lastbootuptime" from the object glob:
(icm ServerNameGoesHere {Get-CimInstance -ClassName win32_operatingsystem}).lastbootuptime
Indecisively, I want to be more flexible:
$a=icm ServerNameGoesHere {Get-CimInstance -ClassName win32_operatingsystem}
$a.version
$a.lastbootuptime
$a.csname
(Makes sense?)
Good luck,
~PhilC