PowerShell Ast for injection detection - powershell

I am generating a ScriptBlock based on DB input which I invoke later in the script. I now want to ensure that a malicious user is not injecting any PS code in the DB varchar field that then gets executed.
First, I filtered the String Script Block for forbidden chars such as $ or ;. But I want to take it one step further and use AST to check if there is any executable code in the DB field.
When I use $Ast.FindAll for a specific element such as ForEachStatementAst it works fine.
However, I also want to be able to detect cmdlets etc in the String.
Examples that should be recognised as being ok:
abc
123
'a','b'
true
Examples that should be recognised as being not ok:
Write-host or Remove-Item or any other get-command cmdlet.
`$(MySubExpression)
When using AST visualisation, I get the same tree for both examples. ('abc', 'Write-Host')
ScriptBlockAst-> NamedBlockAst -> PipelineAst -> CommandAst -> StringConstantExpressionAst
Is there any way I can use AST to determine whether the DB field (or any string) contains only allowed entries such as non PS keywords / cmdlets, numbers etc but nothing that could be used as a PS command and that could be invoked?
The following code works for the test cases but I wonder if this can be achieved in a better way. If Res.count > 0, the input was not ok, if =0, it was ok.
$DebugPreference = 'Continue'
[System.Collections.Generic.List[System.String]]$InputStringList = New-Object -TypeName "System.Collections.Generic.List[System.String]"
$InputStringList.Add("foreach (`$x in #('a','b')){;}")
$InputStringList.Add("New-item -Path 'C:\Test.txt' -ItemType File")
$InputStringList.Add("Write-host 'as'")
$InputStringList.Add("abc")
$InputStringList.Add("a,b,c")
$InputStringList.Add("123")
$InputStringList.Add("true")
[System.Collections.Generic.List[System.Type]]$TypeList = New-Object -TypeName "System.Collections.Generic.List[System.Type]"
$TypeList.Add([System.Management.Automation.Language.StringLiteralToken])
$TypeList.Add([System.Management.Automation.Language.ScriptBlockAst])
$TypeList.Add([System.Management.Automation.Language.NamedBlockAst])
$TypeList.Add([System.Management.Automation.Language.StringConstantExpressionAst])
$TypeList.Add([System.Management.Automation.Language.ConstantExpressionAst])
$TypeList.Add([System.Management.Automation.Language.CommandExpressionAst])
$TypeList.Add([System.Management.Automation.Language.CommandAst])
$TypeList.Add([System.Management.Automation.Language.PipelineAst])
$TypeList.Add([System.Management.Automation.Language.ArrayLiteralAst])
[String[]]$CommandArray = (Get-Command | Select-Object -ExpandProperty 'Name')
[System.Management.Automation.ScriptBlock]$Predicate =
{
param([System.Management.Automation.Language.Ast]$AstObject)
Write-Debug -Message $AstObject.GetType().FullName
if($AstObject -is [System.Management.Automation.Language.StringConstantExpressionAst])
{
if($AstObject.Value -in $CommandArray)
{
return $true
}
else
{
return $false
}
}
else
{
return (-not($AstObject.GetType() -in $TypeList))
}
}
$InputStringList.GetEnumerator() | ForEach-Object -Process `
{
Write-Debug -Message ("Processing string: "+$PsItem.ToString())
$ast = [System.Management.Automation.Language.Parser]::ParseInput($PsItem, [ref]$null, [ref]$null)
$res=$ast.FindAll($Predicate, $true)
Write-Debug -Message $res.count.ToString()
}

As commented, what you trying to do is creating your own restricted languagemode. Meaning that it would probably be easier to invoke the concerned scriptblock in an restricted runspace.
Derived from #mklement0 great answer for Automatically retrieve Allowed Types for Constrained Language mode:
Function Invoke-Restricted {
[CmdletBinding()]param([String]$Expression)
$Restricted = [powershell]::Create()
$Restricted.Runspace.SessionStateProxy.LanguageMode = 'Restricted'
Try { $Restricted.AddScript($expression).Invoke() }
Catch { $PSCmdlet.ThrowTerminatingError($_) }
}
Restricted expression
Invoke-Restricted #'
#{
string = 'abc'
int = 123
array = 'a','b'
hashtable = #{ a = 1; b = 2 }
boolean = $true
}
'#
Yields
Name Value
---- -----
array {a, b}
int 123
boolean True
string abc
hashtable {b, a}
Invalid expression
Invoke-Restricted #'
#{
TimeSpan = [TimeSpan]'12:34:45'
}
'#
Throws an error:
Invoke-Restricted: Exception calling "Invoke" with "0" argument(s): "At line:1 char:1
+ [TimeSpan]"12:34:45"
+ ~~~~~~~~~~
The type TimeSpan is not allowed in restricted language mode or a Data section."
Yet, it has some limitations as it does not prevent e.g. the use of cmdlets.
For an easy and secure way to retrieve a (structured) configuration file I would depend on a serialized format as JSON using the ConvertFrom-Json cmdlet
Related: #12377 Running partly trusted PowerShell code in a restricted security environment.

Related

How to call a script with unknown parameters

I have a script that calls other scripts that other people manage. It's essentially a CI/CD script that gives users the ability to tap into the pipeline.
The issue I'm running into now is that I would like this calling script to implement a couple new parameters. However, the old scripts don't always implement those parameters.
If I call their script that doesn't implement the parameters, I get an error "A parameter cannot be found that matches parameter name 'newparameter'".
Is there a way to dynamically pass in a parameter so that it doesn't fail if the parameter doesn't exist? I don't mind if they don't implement it. It's a bonus parameter that they don't need to use.
Alternately, can I do something like a Get-Command for a custom .ps1 script, to get a list of accepted parameters? With that, I could confirm that a parameter is implemented before I pass it.
This might help you get started, you could use the Parser Class
to get all functions and it's parameters from a script, this answer shows a minimal reproduction. I'll leave it to you to investigate further.
Given myScript.ps1 that has these 3 functions:
function ExampleFunc {
param([int] $param1 = 123, [string] $param2)
}
function ExampleFunc2 {
param([object] $param3, [switch] $param4)
}
function ExampleFunc3 ($param5, [hashtable] $param6 = #{foo = 'var'}) {
}
You can use the ParseFile Method to get the AST, then you can use the .FindAll method to filter for all FunctionDefinitionAst and subsequently find all parameters filtering for all ParameterAst.
using namespace System.Management.Automation.Language
$ast = [Parser]::ParseFile('path\to\myScript.ps1', [ref] $null, [ref] $null)
$ast.FindAll({ $args[0] -is [FunctionDefinitionAst] }, $true) | ForEach-Object {
$out = [ordered]#{ Function = $_.Name }
$_.FindAll({ $args[0] -is [ParameterAst] }, $true) | ForEach-Object {
$out['ParameterName'] = $_.Name.VariablePath
$out['Type'] = $_.StaticType
$out['DefaultValue'] = $_.DefaultValue
[pscustomobject] $out
}
} | Format-Table
Above code would result in the following for myScript.ps1:
Function ParameterName Type DefaultValue
-------- ------------- ---- ------------
ExampleFunc param1 System.Int32 123
ExampleFunc param2 System.String
ExampleFunc2 param3 System.Object
ExampleFunc2 param4 System.Management.Automation.SwitchParameter
ExampleFunc3 param5 System.Object
ExampleFunc3 param6 System.Collections.Hashtable #{foo = 'var'}
The same could be accomplished using Get-Command:
(Get-Command 'fullpath\to\myScript.ps1').ScriptBlock.Ast.FindAll({
... same syntax as before ... }, $true # or $false for non-recursive search
)

Powershell Global Variable usage as parameter to argument

$global:af_fp = "C:\Path\to\folder\"
Function function-name {
do things …
$global:af_fp = $global:af_fp + $variableFromDo_things + "_AF.csv"
}
function-name | ConvertTo-CSV -NoTypeInformation | Add-Content -Path $($af_fp)
Above is the generalized (and abbreviated) script contents for a powershell script.
Every time I run the script in this way, I get the following error:
Add-Content : Could not find a part of the path 'C:\Users\timeuser\Documents\'.
At C:\Users\timeuser\Documents\get_software.ps1:231 char:51
+ ... ware | ConvertTo-CSV -NoTypeInformation | Add-Content -Path $($af_fp)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Users\timeuser\Documents\:String) [Add-Content], DirectoryNotFoundException
+ FullyQualifiedErrorId : GetContentWriterDirectoryNotFoundError,Microsoft.PowerShell.Commands.AddContentCommand
When I run
Get-Variable -Scope global
after running the script and seeing the error, the variable af_fp contains exactly the information I am seeking for the file name, however, the error shows the variable contents ending in ':String'.
To confuse me even more, if I comment out the lines containing '$global:...' and re-run the same script, IT ACTUALL RUNS AND SAVES THE FILE USING THE LINE
function-name | ConvertTo-CSV -NoTypeInformation | Add-Content -Path $($af_fp)
AS INTENDED. Of course, I had to run the script and watch it error first, then re-run the script with the global variable declaration and update commented out for it to actually work. I want to run the script ONCE and still get the same results.
FYI, I am a complete noob to powershell, but very familiar with the concept of variable scope.....but why is this global not working when initially created and updated, but then work the second time around, when, as far as I can tell, the CONTENT AND SCOPE of the global remains the same...…. any assistance to finding a solution to this small issue would be greatly appreciated; I have tried sooooo may different methods from inquiries through here and on Google...…..
EDIT: not sure why this will matter, because the script ran before as intended when I explicitly typed the parameter for -Path as 'C:\path\to\file'. The ONLY CHANGES MADE to the original, working script (below) were my inclusion of the global variable declaration, the update to the contents of the global variable (near the end of the function), and the attempt to use the global variable as the parameter to -Path, that is why I omitted the script:
'''
$global:af_fp = "C:\Users\timeuser\Documents\"
Function Get-Software {
[OutputType('System.Software.Inventory')]
[Cmdletbinding()]
Param(
[Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[String[]]$Computername = $env:COMPUTERNAME
)
Begin {
}
Process {
ForEach ($Computer in $Computername) {
If (Test-Connection -ComputerName $Computer -Count 1 -Quiet) {
$Paths = #("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", "SOFTWARE\\Wow6432node\\Microsoft\\Windows\\CurrentVersion\\Uninstall")
ForEach ($Path in $Paths) {
Write-Verbose "Checking Path: $Path"
# Create an instance of the Registry Object and open the HKLM base key
Try {
$reg = [microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine', $Computer, 'Registry64')
}
Catch {
Write-Error $_
Continue
}
# Drill down into the Uninstall key using the OpenSubKey Method
Try {
$regkey = $reg.OpenSubKey($Path)
# Retrieve an array of string that contain all the subkey names
$subkeys = $regkey.GetSubKeyNames()
# Open each Subkey and use GetValue Method to return the required values for each
ForEach ($key in $subkeys) {
Write-Verbose "Key: $Key"
$thisKey = $Path + "\\" + $key
Try {
$thisSubKey = $reg.OpenSubKey($thisKey)
# Prevent Objects with empty DisplayName
$DisplayName = $thisSubKey.getValue("DisplayName")
If ($DisplayName -AND $DisplayName -notmatch '^Update for|rollup|^Security Update|^Service Pack|^HotFix') {
$Date = $thisSubKey.GetValue('InstallDate')
If ($Date) {
Try {
$Date = [datetime]::ParseExact($Date, 'yyyyMMdd', $Null)
}
Catch {
Write-Warning "$($Computer): $_ <$($Date)>"
$Date = $Null
}
}
# Create New Object with empty Properties
$Publisher = Try {
$thisSubKey.GetValue('Publisher').Trim()
}
Catch {
$thisSubKey.GetValue('Publisher')
}
$Version = Try {
#Some weirdness with trailing [char]0 on some strings
$thisSubKey.GetValue('DisplayVersion').TrimEnd(([char[]](32, 0)))
}
Catch {
$thisSubKey.GetValue('DisplayVersion')
}
$UninstallString = Try {
$thisSubKey.GetValue('UninstallString').Trim()
}
Catch {
$thisSubKey.GetValue('UninstallString')
}
$InstallLocation = Try {
$thisSubKey.GetValue('InstallLocation').Trim()
}
Catch {
$thisSubKey.GetValue('InstallLocation')
}
$InstallSource = Try {
$thisSubKey.GetValue('InstallSource').Trim()
}
Catch {
$thisSubKey.GetValue('InstallSource')
}
$HelpLink = Try {
$thisSubKey.GetValue('HelpLink').Trim()
}
Catch {
$thisSubKey.GetValue('HelpLink')
}
$Object = [pscustomobject]#{
#Potential Candidate for AssetID in the TIME system
AssetID = $Computer
#String that contains word or word combinations for the product field of CPE WFN; may also contain the valid values necessary for update, edition, language, sw_edition, target_hw/sw fields as well.
cpeprodinfo = $DisplayName
cpeversion = $Version
InstallDate = $Date
cpevendor = $Publisher
UninstallString = $UninstallString
InstallLocation = $InstallLocation
InstallSource = $InstallSource
HelpLink = $thisSubKey.GetValue('HelpLink')
EstimatedSizeMB = [decimal]([math]::Round(($thisSubKey.GetValue('EstimatedSize') * 1024) / 1MB, 2))
}
$Object.pstypenames.insert(0, 'System.Software.Inventory')
Write-Output $Object
}
}
Catch {
Write-Warning "$Key : $_"
}
}
}
Catch { }
$reg.Close()
}
}
Else {
Write-Error "$($Computer): unable to reach remote system!"
}
$global:af_fp = $global:af_fp + $Computer + "_AF.csv"
}
}
}
Get-Software | ConvertTo-CSV -NoTypeInformation | Add-Content -Path $($af_fp)
'''
IGNORE FORMATTING PLEASE- HAD TROUBLE MAKING INDENTS CORRECTLY FROM COPY-PASTE AND RESTRICTIONS ON SITE FOR CODE BLOCKS.....
NOTE: the ONLY changes I made, that I am asking about, are the global declaration, the global variable update in the function, and the attempt to use the global variable for the -Path parameter....script otherwise runs and will even run WITH THE LAST LINE AS IS if I ran it and errored the first time.....not sure how the addition script will help in any way, shape, or form!
With a little effort, Nasir's solution worked! HOWEVER, I ran across a sample file that had a way of adding to a parameter that inspired me to make a change to my ORIGINAL, that also worked: remove global variable from script entirely and add this code the very end:
$file_suffix = '_AF.csv'
Get-Software | ConvertTo-CSV -NoTypeInformation | Add-Content -Path $env:COMPUTERNAME$file_suffix
In this way, I was able to accomplish exactly what I was setting out to do! Thanks Nasir for your response as well! I was able to also make that work as intended!
Global variables are generally frowned upon, since they often lead to poor scripts, with hard to debug issues.
It seems like your function returns some stuff, which you need to write to a file, the name of which is also generated by the same function. You can try something like this:
function function-name {
param($PathPrefix)
#do things
[pscustomobject]#{"DoThings_data" = $somevariablefromDoThings; "Filename" = "$($PathPrefix)$($variableFromDo_Things)_AF.csv"}
}
function-name -PathPrefix "C:\Path\to\folder\" | Foreach-Object { $_.DoThings_data | Export-Csv -Path $_.Filename -NoTypeInformation }
Or just have your function write the CSV data out and then return the data if you need to further process it outside the function.
Edit: this is just me extrapolating from partial code you have provided. To Lee_Dailey's point, yes, please provide more details.

PowerShell analysing variables and objects

I found that I do .GetType() and | Get-Member a lot, and sometimes I forget that it's .GetType and I try .Get-Type duh! error!), so I've been writing a function to try and gather that info quickly. This has proven to be quite useful when working on the console (I make sure to put the core command before each output so that I never forget the connection to real commands, so more of a techy summary to keep me connected to the language).
I'm curious if there are additional compound commands to extract useful generic information that we could use to report on the structure of a given object (things that we can quickly obtain in a nicely compact summary format even though more complex commands are required for some useful insight about given objects)?
• $a = #(1,2,"x") ; obj $a. This returns 71 Methods (System.String and System.Int32) Types, so I've removed duplicates down to 50 (good to quickly see what is usable but maybe good to somehow also mention the different Types that are contained in that array?).
• Some input will break the function of course, but even a ScriptBlock example like this also works fine obj {$a; $x}. You can even do things like obj "".GetType() to see the Methods and properties in there.
• The use of .Module in the GetType() might be redundant as usually outputs CommonLanguageRuntimeLibrary, but maybe other useful information from these members (everything is useful at different times of course, but I'm curious for generic summary output)?
• In general any improvements or other compound commands that you use or could be useful to crack open object information in a quick summary view would be great to know? :-)
Updated with -Force that #Clint suggested:
function obj ($cmd) {
if ($cmd -eq $null) { Write-Host "Object is `$null" ; return }
Write-Host "Contents:" -F Cyan
$cmd
""
Write-Host "(`$object).GetType()" -F Cyan -NoNewline ; Write-Host " :: [BaseType|Name|IsPublic|IsSerial|Module]"
($cmd).GetType() | % { "$($_.BaseType), $($_.Name), $($_.IsPublic), $($_.IsSerializable), $($_.Module)" }
""
Write-Host "`$object | Get-Member -Force" -F Cyan
$m = "" ; $p = "" ; $pp = "" ; $np = "" ; $sp = ""
$msum = 0 ; $psum = 0 ; $ppsum = 0 ; $npsum = 0 ; $spsum = 0
$($cmd | Get-Member -Force) | % {
if ($_.MemberType -eq "Method") { if(!($m -like "*$($_.Name),*")) { $m += "$($_.Name), " ; $msum++ } }
if ($_.MemberType -eq "Property") { if(!($p -like "*$($_.Name),*")) { $p += "$($_.Name), " ; $psum++ } }
if ($_.MemberType -eq "ParameterizedProperty") { if(!($pp -like "*$($_.Name),*")) { $pp += "$($_.Name), " ; $ppsum++} }
if ($_.MemberType -eq "NoteProperty") { if(!($np -like "*$($_.Name),*")) { $np += "$($_.Name), " ; $npsum++ } }
if ($_.MemberType -eq "ScriptProperty") { if(!($sp -like "*$($_.Name),*")) { $sp += "$($_.Name), " ; $npsum++ } }
}
if($msum -ne 0) { Write-Host ":: Method [$msum] => $($m.TrimEnd(", "))" }
if($psum -ne 0) { Write-Host ":: Property [$psum] => $($p.TrimEnd(", "))" }
if($ppsum -ne 0) { Write-Host ":: ParameterizedProperty [$ppsum] => $($pp.TrimEnd(", "))" }
if($npsum -ne 0) { Write-Host ":: NoteProperty [$npsum] => $($np.TrimEnd(", "))" }
if($spsum -ne 0) { Write-Host ":: ScriptProperty [$spsum] => $($sp.TrimEnd(", "))" }
""
}
An example of output:
C:\> $a = #(123,"x")
C:\> def $a
Contents:
123
x
($object).GetType() :: [BaseType|Name|IsPublic|IsSerial|Module]
array, Object[], True, True, CommonLanguageRuntimeLibrary
$object | Get-Member -Force
:: Method [50] => CompareTo, Equals, GetHashCode, GetType, GetTypeCode, ToBoolean, ToByte, ToChar, ToDateTime, ToDecimal, ToDouble, ToInt16,
ToInt32, ToInt64, ToSByte, ToSingle, ToString, ToType, ToUInt16, ToUInt32, ToUInt64, Clone, Contains, CopyTo, EndsWith, GetEnumerator,
get_Chars, get_Length, IndexOf, IndexOfAny, Insert, IsNormalized, LastIndexOf, LastIndexOfAny, Normalize, PadLeft, PadRight, Remove, Replace,
Split, StartsWith, Substring, ToCharArray, ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant, Trim, TrimEnd, TrimStart
:: Property [1] => Length
:: ParameterizedProperty [1] => Chars
You have summarized it quite well as it is, you might as well add
object | gm -Force # To add members that are usually hidden by default
The Get-Member command uses the Force parameter to add the intrinsic
members and compiler-generated members of the objects to the display.
Get-Member gets these members, but it hides them by default.
Intrinsic members (PSBase, PSAdapted, PSObject, PSTypeNames)
Compiler-generated get_ and set_ methods
MSDN Doc
object.PSObject #Wraps the information pertaining to different members
object.PSObject | Select-Object -ExpandProperty Properties #You can do the same for other members
$object |gm -force -static #even with force the static members are not listed by default, so we need to explicitly mention static
maybe good to somehow also mention the different Types that are contained in that array?).
$object | ForEach-Object {$_.GetType().FullName}
OffTopic, additionally, if you want to capture the time taken for the function to execute
$sw = [Diagnostics.Stopwatch]::StartNew()
$sw.Stop()
Write-Host "Execution time" $sw.Elapsed.Milliseconds "ms"
Knowing that PowerShell isn't very clear in displaying an object (as e.g.: '', $Null, #() and #{$Null) all result in an empty line, or nothing at all), I think you need start with the first paragraph of you question and visualize the types and the structure of your objects in question and later concern with the methods which might differ on every
level in the object structure.
*note: you might also reveal the types by using .PSTypeNames, which doesn't error on a $Null (presuming that strict mode isn't switch on).
Taken a little bit more advanced example: $a = #($Null, 1, '2', #(3))
Write-Host
The Write-Host cmdlet (used by your function) doesn't reveal much where is concerns types and the structure of the object:
(Besides you should try to avoid Write-Host as it is Host specific)
PS C:\> $a
1
2
3
PS C:\> Write-Host $a
1 2 3
To better understand such an object, you might want to serializes it.
Quote from WikiPedia:
In computer science, in the context of data storage, serialization (or
serialisation) is the process of translating data structures or object
state into a format that can be stored (for example, in a file or
memory buffer) or transmitted (for example, across a network
connection link) and reconstructed later (possibly in a different
computer environment)
This means that the string representation of the object basically contains all the information to rebuild (and to understand) the object (or at least to a certain level).
There are a few serializers that might come at hand here:
ConvertTo-Json
The ConvertTo-Json cmdlet converts any object to a string in JavaScript Object Notation (JSON) format a therefore might be handy to get an idea of the structure but not for the PowerShell types:
PS C:\> ConvertTo-Json $a
[
null,
1,
"2",
[
3
]
]
ConvertTo-Xml
The ConvertTo-Xml cmdlet creates an XML-based representation of an object which is pretty verbose:
PS C:\> ConvertTo-Xml $a -as String
<?xml version="1.0" encoding="utf-8"?>
<Objects>
<Object Type="System.Object[]">
<Property />
<Property Type="System.Int32">1</Property>
<Property Type="System.String">2</Property>
<Property Type="System.Object[]">
<Property Type="System.Int32">3</Property>
</Property>
</Object>
</Objects>**strong text**
PSSerializer
This (.Net) class provides public functionality for serializing a PSObject and is internally used by PowerShell:
PS C:\> [System.Management.Automation.PSSerializer]::Serialize($a)
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
<Obj RefId="0">
<TN RefId="0">
<T>System.Object[]</T>
<T>System.Array</T>
<T>System.Object</T>
</TN>
<LST>
<Nil />
<Obj RefId="1">
<I32>1</I32>
</Obj>
<S>2</S>
<Obj RefId="2">
<TNRef RefId="0" />
<LST>
<I32>3</I32>
</LST>
</Obj>
</LST>
</Obj>
</Objs>
ConvertTo-Expression
The ConvertTo-Expression cmdlet originated from the question: Save hash table in PowerShell object notation (PSON) and gives you a PowerShell representation of the object:
Implicit:
PS C:\> ConvertTo-Expression $a
$Null,
1,
'2',
(,3)
Explicit:
PS C:\> ConvertTo-Expression $a -Strong
[array](
$Null,
[int]1,
[string]'2',
[array][int]3
)
To explorer .Net classes that are not native to PowerShell:
*note that you might not reconstruct from -Explore expressions
PS C:\> Get-Service | Select -First 1 | ConvertTo-Expression -Strong -Explore
[System.ServiceProcess.ServiceController]#{
'UserName' = [string]''
'Description' = [string]'Runtime for activating conversational agent applications'
'DelayedAutoStart' = [bool]$False
'BinaryPathName' = [string]'C:\WINDOWS\system32\svchost.exe -k AarSvcGroup -p'
'StartupType' = [Microsoft.PowerShell.Commands.ServiceStartupType]3
'CanPauseAndContinue' = [bool]$False
'CanShutdown' = [bool]$False
'CanStop' = [bool]$False
'DisplayName' = [string]'Agent Activation Runtime_131b90'
'DependentServices' = [System.ServiceProcess.ServiceController[]]#()
'MachineName' = [string]'.'
'ServiceName' = [string]'AarSvc_131b90'
'ServicesDependedOn' = [System.ServiceProcess.ServiceController[]]#()
'StartType' = [System.ServiceProcess.ServiceStartMode]3
'ServiceHandle' = $Null
'Status' = [System.ServiceProcess.ServiceControllerStatus]1
'ServiceType' = [System.ServiceProcess.ServiceType]224
'Site' = $Null
'Container' = $Null
}
To drill down in known (accelerated) PowerShell types:
PS C:\>Get-Date | Select-Object -Property * | ConvertTo-Expression
Date : 1963-10-07 12:00:00 AM
DateTime : Monday, October 7, 1963 10:47:00 PM
Day : 7
DayOfWeek : Monday
DayOfYear : 280
DisplayHint : DateTime
Hour : 22
Kind : Local
Millisecond : 0
Minute : 22
Month : 1
Second : 0
Ticks : 619388596200000000
TimeOfDay : 22:47:00
Year : 1963

Check if a string is not NULL or EMPTY

In below code, I need to check if version string is not empty then append its value to the request variable.
if ([string]::IsNullOrEmpty($version))
{
$request += "/" + $version
}
How to check not in if condition?
if (-not ([string]::IsNullOrEmpty($version)))
{
$request += "/" + $version
}
You can also use ! as an alternative to -not.
You don't necessarily have to use the [string]:: prefix. This works in the same way:
if ($version)
{
$request += "/" + $version
}
A variable that is null or empty string evaluates to false.
As in many other programming and scripting languages you can do so by adding ! in front of the condition
if (![string]::IsNullOrEmpty($version))
{
$request += "/" + $version
}
If the variable is a parameter then you could use advanced function parameter binding like below to validate not null or empty:
[CmdletBinding()]
Param (
[parameter(mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$Version
)
if (!$variablename)
{
Write-Host "variable is null"
}
I hope this simple answer will resolve the question.
Source
I would define $Version as a string to start with
[string]$Version
and if it's a param you can use the code posted by Samselvaprabu
or if you would rather not present your users with an error you can do something like
while (-not($version)){
$version = Read-Host "Enter the version ya fool!"
}
$request += "/" + $version
You can use the [string]::IsNullOrEmpty($version) method if it is a string.
But, I was looking for a universal way to check nulls (regardless of data type)
in Powershell. Checking for null (or not null) values in
PowerShell is tricky. Using ($value -eq $null) or ($value -ne
$null) does not always work. Neither does if($value). Using them
can even cause problems later on.
Just read this Microsoft article below (IN IT'S ENTIRETY) to get a grasp
of how tricky nulls can be in Powershell.
[https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-null?view=powershell-7.1][1]
I wrote these two functions below for checking for null (or not null) values
in PowerShell. I "believe" they should work for any and all values
and data types.
I hope someone finds them helpful.
I am not sure why MS hasn't put something like this into PowerShell natively to
make handling nulls easier (and less dangerous) in PowerShell.
I hope this helps someone.
If anyone knows of an unseen "pitfall" or problem with this method,
please post a comment here so we can know that.
Thanks!
<#
*********************
FUNCTION: ValueIsNull
*********************
Use this function ValueIsNull below for checking for null values
rather using -eq $null or if($value) methods. Those may not work as expected.
See reference below for more details on $null values in PowerShell.
[https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-null?view=powershell-7.1][1]
An if statement with a call to ValueIsNull can be written like this:
if (ValueIsNull($TheValue))
#>
function ValueIsNull {
param($valueToCheck)
# In Powershell when a parameter of a function does not have a data type defined,
# it will create the parameter as a PSObject. It will do this for
# an object, an array, and a base date type (int, string, DateTime, etc.)
# However if the value passed in is $null, then it will still be $null.
# So, using a function to check null gives us the ability to determine if the parameter
# is null or not by checking if the parameter is a PSObject or not.
# This function could be written more efficiently, but intentionally
# putting it in a more readable format.
# Special Note: This cannot tell the difference between a parameter
# that is a true $null and an undeclared variable passed in as the parameter.
# ie - If you type the variable name wrong and pass that in to this function it will see it as a null.
[bool]$returnValue = $True
[bool]$isItAnObject=$True
[string]$ObjectToString = ""
try { $ObjectToString = $valueToCheck.PSObject.ToString() } catch { $isItAnObject = $false }
if ($isItAnObject)
{
$returnValue=$False
}
return $returnValue
}
<#
************************
FUNCTION: ValueIsNotNull
************************
Use this function ValueIsNotNull below for checking values for
being "not-null" rather than using -ne $null or if($value) methods.
Both may not work as expected.
See notes on ValueIsNull function above for more info.
ValueIsNotNull just calls the ValueIsNull function and then reverses
the boolean result. However, having ValueIsNotNull available allows
you to avoid having to use -eq and\or -ne against ValueIsNull results.
You can disregard this function and just use !ValueIsNull($value).
But, it is my preference to have both for easier readability of code.
An if statement with a call to ValueIsNotNull can be written like this:
if (ValueIsNotNull($TheValue))
#>
function ValueIsNotNull {
param($valueToCheck)
[bool]$returnValue = !(ValueIsNull($valueToCheck))
return $returnValue
}
You can use the following list of calls to ValueIsNull to test it out.
$psObject = New-Object PSObject
Add-Member -InputObject $psObject -MemberType NoteProperty -Name customproperty -Value "TestObject"
$valIsNull = ValueIsNull($psObject)
$props = #{
Property1 = 'one'
Property2 = 'two'
Property3 = 'three'
}
$otherPSobject = new-object psobject -Property $props
$valIsNull = ValueIsNull($otherPSobject)
# Now null the object
$otherPSobject = $null
$valIsNull = ValueIsNull($otherPSobject)
# Now an explicit null
$testNullValue = $null
$valIsNull = ValueIsNull($testNullValue)
# Now a variable that is not defined (maybe a type error in variable name)
# This will return a true because the function can't tell the difference
# between a null and an undeclared variable.
$valIsNull = ValueIsNull($valueNotDefine)
[int32]$intValueTyped = 25
$valIsNull = ValueIsNull($intValueTyped)
$intValueLoose = 67
$valIsNull = ValueIsNull($intValueLoose)
$arrayOfIntLooseType = 4,2,6,9,1
$valIsNull = ValueIsNull($arrayOfIntLooseType)
[int32[]]$arrayOfIntStrongType = 1500,2230,3350,4000
$valIsNull = ValueIsNull($arrayOfIntStrongType)
#Now take the same int array variable and null it.
$arrayOfIntStrongType = $null
$valIsNull = ValueIsNull($arrayOfIntStrongType)
$stringValueLoose = "String Loose Type"
$valIsNull = ValueIsNull($stringValueLoose)
[string]$stringValueStrong = "String Strong Type"
$valIsNull = ValueIsNull($stringValueStrong)
$dateTimeArrayLooseValue = #("1/1/2017", "2/1/2017", "3/1/2017").ForEach([datetime])
$valIsNull = ValueIsNull($dateTimeArrayLooseValue)
# Note that this has a $null in the array values. Still returns false correctly.
$stringArrayLooseWithNull = #("String1", "String2", $null, "String3")
$valIsNull = ValueIsNull($stringArrayLooseWithNull)

compile oracle form using powershell script

I have many oracle forms in one folder and I want to compile those forms through frmcmp command in powershell script.
I have written a powershell script which is following
$module="module="
get-childitem "C:\forms\fortest" -recurse |
where { $_.extension -eq ".fmb" } |
foreach {
C:\Oracle\Middleware\Oracle_FRHome1\BIN\frmcmp $module $_.FullName userid=xyz/xyz#xyz Output_File=C:\forms\11\common\fmx\$_.BaseName+'.fmx'
}
but this one is not working. i am new in powershell.
but when I try to compile a single form through command prompt its working like following.
frmcmp module=C:\forms\src\xyz.fmb userid=xyz/xyz#xyz Output_File=C:\forms\11\common\fmx\xyz.fmx
When you want to use variables inside a string in PowerShell you have different options. To start with, you will always need to use " as opposed to ' to wrap the string, if you want variables in your string.
$myVariable = "MyPropertyValue"
Write-Host "The variable has the value $MyVariable"
The above code would yield the output:
The variable has the value MyPropertyValue
If you want to use a property of a variable (or any expression) and insert it into the string, you need to wrap it in the string with $(expression goes here), e.g.
$MyVariable = New-Object PSObject -Property #{ MyPropertyName = 'MyPropertyValue' }
# The following will fail getting the property since it will only consider
# the variable name as code, not the dot or the property name. It will
# therefore ToString the object and append the literal string .MyPropertyName
Write-Host "Failed property value retrieval: $MyVariable.MyPropertyName"
# This will succeed, since it's wrapped as code.
Write-Host "Successful property value retrieval: $($MyVariable.MyPropertyName)"
# You can have any code in those wrappers, for example math.
Write-Host "Maths calculating: 3 * 27 = $( 3 * 27 )"
The above code would yield the following output:
Failed property value retrieval: #{MyPropertyName=MyPropertyValue}.MyPropertyName
Successful property value retrieval: MyPropertyValue
Maths calculating: 3 * 27 = 81
I generally try to use the Start-Process cmdlet when I start processes in PowerShell, since it gives me the possibility of additional control over the process started. This means that you could use something similar to the following.
Get-ChildItem "C:\forms\fortest" -Filter "*.fmb" -recurse | Foreach {
$FormPath = $_.FullName
$ResultingFileName = $_.BaseName
Start-Process -FilePath "C:\Oracle\Middleware\Oracle_FRHome1\BIN\frmcmp.exe" -ArgumentList "module=$FormPath", "userid=xyz/xyz#xyz", "Output_File=C:\forms\11\common\fmx\$ResultingFileName.fmx"
}
You could also add the -Wait parameter to the Start-Process command, if you want to wait with compilation of the next item until the current compilation has completed.