Powershell scripting for url custom monitoring - powershell

I am trying to build a custom script for URL monitoring. I am able to run the URL's from the file and enter the same in a logfile(named with time stamp).
Till here I have completed
Issue is when I compare the values from present(present timestamp) and previous logfile(previous timestamp).
This portion is not working fine. Please help me correct it.
Here is my code trying to compare value line by line from present logfile and previous logfile and run commands to generate output:
# New log is new logfile data
$Newlog = Get-Content $URLlogfile
$old_file = Dir C:\Scripts\logs | Sort CreationTime -Descending | Select Name -last 1
# Old log is Old logfile data
$oldlog = Get-Content $old_file -ErrorAction SilentlyContinue
Foreach($logdata in $Newlog) {
$url = ($logdata.Split(" "))[0]
$nodename = ($logdata.Split(" "))[1]
$statuscheck = ($logdata.Split(" "))[2]
$description = ($logdata.Split(" "))[3]
$statuscode = ($logdata.Split(" "))[4]
Foreach($log1data in $oldlog) {
$url1 = ($log1data.Split(" "))[0]
$nodename1 = ($log1data.Split(" "))[1]
$statuscheck1 = ($log1data.Split(" "))[2]
$description1 = ($log1data.Split(" "))[3]
$statuscode1 = ($log1data.Split(" "))[4]
While ($url = $url1) {
if ($statuscheck = $statuscheck1 ) {
write-output "output is same"
} elseif ($statuscheck = Fail) {
While ($statuscheck1 = Pass) {
write-output "$url is down at $nodename1- testing event sent"
}
} elseif ($statuscheck = Pass) {
While ($statuscheck1 = Fail) {
write-output "$url is up at $nodename1- testing event sent"
}
}
}
Break
}
}
#At end am clearing the old logs except present one
dir C:\Scripts\logs -recurse | where { ((get-date)-$_.creationTime).minutes -gt 3 } | remove-item -force

Per the comment from BenH, the following part of your code needs correcting as follows:
If ($url -eq $url1) {
if ($statuscheck -eq $statuscheck1 ) {
write-output "output is same"
} elseif ($statuscheck -eq 'Fail' -and $statuscheck1 -eq 'Pass') {
write-output "$url is down at $nodename1- testing event sent"
} elseif ($statuscheck -eq 'Pass' -and $statuscheck1 -eq 'Fail') {
write-output "$url is up at $nodename1- testing event sent"
}
}
Corrections:
In your comparison statements the = needs to be -eq. In PowerShell = always assigns a value.
In your comparison statements Pass and Fail need to be surrounded by single quotes so they are treated as strings (otherwise they are treated like function statements, for functions which don't exist).
I've replaced the While statements with If statements. I'm not sure what the intent of those was but I think they'd just get stuck in an infinite loop as the variable they test is never changed from within the loop.

Related

Powershell: Arrays index acess and check if previous is substring to next

I need to link the job to the subjob: the job is of this format for example ACGN100Q while the subjobs that are attached are sequential and of this format: ACGN-100Q-000T;ACGN-100Q-010T;ACGN-100Q-020T;ACGN-100Q-030T
In my csv file the type of this job ACGN100Q is "TechnologyInteraction" while the subjobs are of type "TechnologyService". I am developing a script that allows me to say for example that the link between ACGN-100Q-000T and ACGN-100Q-010T is of type "TrigerringRelation" and the link between ACGN100Q and ACGN-100Q-000T is of type "RealizationRelation". I need help because I can't make the link.
Here is my starting csv file :
newElements.csv
ID,"Type","Name","Documentation"
eb214110-2b6a-48b2-ba5a-7c13dc3bba39,"TechnologyInteraction","ACGN100Q","Begin Of JobStream"
a46681e7-19a8-4fc5-b747-09679c15ff26,"TechnologyService","ACGN-100Q-000T","Transfert UDM (xACGN000)"
85761a09-1145-4037-a527-66a743def45f,"TechnologyService","ACGN-100Q-010T","move fichier REF to work"
27b126fb-c708-427d-b0a6-ce4747114ac4,"TechnologyService","ACGN-100Q-020T","w_read_account"
bb0c5e42-5fad-4bd9-8ee9-f41d0b824e82,"TechnologyService","ACGN-100Q-030T","w_read_referential"
0b8b76e3-62fa-4527-9f05-2eb4dbaa8e97,"TechnologyService","ACGN-100Q-040T","w_load_CompanyGroup"
1f487986-3cac-4af8-bda2-6400a1c71f48,"TechnologyService","ACGN-100Q-050T","w_load_Company"
And I want to get a file that looks like this:
relation.csv
"ID","Type","Name","Documentation","Source","Target"
"New ID","RealizationRelationship","","","eb214110-2b6a-48b2-ba5a-7c13dc3bba39","a46681e7-19a8-4fc5-b747-09679c15ff26"
"New ID","TriggeringRelationship","","","a46681e7-19a8-4fc5-b747-09679c15ff26","85761a09-1145-4037-a527-66a743def45f"
"New ID","TriggeringRelationship","","","85761a09-1145-4037-a527-66a743def45f","27b126fb-c708-427d-b0a6-ce4747114ac4"
"New ID","TriggeringRelationship","","","27b126fb-c708-427d-b0a6-ce4747114ac4","bb0c5e42-5fad-4bd9-8ee9-f41d0b824e82"
"New ID","TriggeringRelationship","","","bb0c5e42-5fad-4bd9-8ee9-f41d0b824e82","0b8b76e3-62fa-4527-9f05-2eb4dbaa8e97"
"New ID","TriggeringRelationship","","","0b8b76e3-62fa-4527-9f05-2eb4dbaa8e97","1f487986-3cac-4af8-bda2-6400a1c71f48"
$result= #()
function linkedRelationCsvToElementsCsv{
#relations.csv headers are ID,"Type","Name","Documentation","Source","Target"
$Type=#()
$Name=#()
$ID=#()
$Documentation=#()
#$pattern="^(WEBX|DWHS|COGN|CLOT|CLAI|BTRE|BISI|BDDO|ARXL|AGSO|AGPC|ACTO|FNET|ARX|AGS|INF|CLA|MEM|SWA|REX)-"
$newElementsCsv=Import-CSV $env:USERPROFILE\Desktop\Archi\newElements.csv |sort ID,"Type","Name","Documentation" -Unique
# Check if type is TechnologyInteraction or TechnologyService and link :TechnologyService to TechnologyInteraction and TechnologyInteraction to TWS id's
ForEach ($line in $newElementsCsv){
$Type += $line.Type
$Name += $line.Name
$ID +=$line.ID
$Documentation += $_.Documentation
#Search for element type in elements.csv
for( $index=0; $index -le $Name.length-1; $index++){
if($Type[$index] -eq 'TechnologyInteraction' -or $Type[$index] -eq 'TechnologyEvent' ){
Write-Warning "Case type TechnologyInteraction founded, new type of RealizationRelationship created "
# if the job is of type "TechnologyInteraction" or "TechnologyEvent", we link it to the TWS id's(TechnologyCollaboration,ef2f510b-924b-439d-8720-0183c7294eb3) in archi.
$newArrayResult= New-Object PsObject -Property #{ID=[guid]::NewGuid().ToString(); "Type"="RealizationRelationship"; "Name"=$Name[$index]; "Documentation"=$Documentation[$index]; "Source"="ef2f510b-924b-439d-8720-0183c7294eb3"; "Target"=$ID[$index]}
$result = $result + $newArrayResult
}elseif ($Type[$index][0].Type -eq 'TechnologyService' -and$Type[$index][1].Type -eq 'TechnologyService' ){
Write-Warning "Case type TechnologyService founded, new type of TriggeringRelationship created "
$newArrayResult2 = New-Object PsObject -Property #{ID=[guid]::NewGuid().ToString(); "Type"="TriggeringRelationship"; "Name"=""; "Documentation"=""; "Source"=$line[$index][0].ID; "Target"=$line[$index][1].ID}
$result = $result + $newArrayResult2
}
}
}
$result |Select-Object -Property ID,"Type","Name","Documentation","Source","Target"| Export-Csv $env:USERPROFILE\Desktop\Archi\relation.csv -NoTypeInformation -Encoding UTF8
}linkedRelationCsvToElementsCsv # Call the function
> the elseIf() not return value.
Thanks you in advance.
The following code generates exactly the output you want for exactly the input you've given. There might be unexpected edge cases, so you should write some tests (e.g. with Pester) to confirm it behaves how you want it to in those edge cases.
The key is that the output for any row includes the ID of the previous row as well as the current row, so we keep the previous row in a variable during the foreach loop so we can inspect it when we process the next row, and the Type in the output just depends on the Type of the previous row.
Note that I've also moved the conversion to / from csv out of the main function so it's easier to unit test the function in isolation.
function ConvertTo-ElementItems
{
param
(
[object[]] $Relations
)
$jobTypes = #( "TechnologyInteraction", "TechnologyEvent" );
$subTypes = #( "TechnologyService" );
$previousItem = $null;
foreach( $item in $Relations )
{
if( $item.Type -in $jobTypes )
{
# start of a new job, but don't output anything
}
elseif( $item.Type -notin $subTypes )
{
# not a subjob type that we recognise
throw "unrecognised subjob type '$($item.Type)' for subjob '$($item.ID)'";
}
elseif( $null -eq $previousItem )
{
# we've got a subjob, but there was no previous job or subjob
throw "no preceding item for subjob '$($item.ID)'";
}
elseif( $previousItem.Type -in $jobTypes )
{
# this is the first subjob after the parent job
new-object PSCustomObject -Property ([ordered] #{
"ID" = "New ID"
"Type" = "RealizationRelationship"
"Name" = "";
"Documentation" = ""
"Source" = $previousItem.ID
"Target" = $item.ID
});
}
else
{
# the preceding item was a subjob as well
new-object PSCustomObject -Property ([ordered] #{
"ID" = "New ID"
"Type" = "TriggeringRelationship"
"Name" = ""
"Documentation" = ""
"Source" = $previousItem.ID
"Target" = $item.ID
});
}
$previousItem = $item;
}
}
And here's an example of using the function:
$ErrorActionPreference = "Stop";
Set-StrictMode -Version "Latest";
$inputCsv = #"
ID,"Type","Name","Documentation"
eb214110-2b6a-48b2-ba5a-7c13dc3bba39,"TechnologyInteraction","ACGN100Q","Begin Of JobStream"
a46681e7-19a8-4fc5-b747-09679c15ff26,"TechnologyService","ACGN-100Q-000T","Transfert UDM (xACGN000)"
85761a09-1145-4037-a527-66a743def45f,"TechnologyService","ACGN-100Q-010T","move fichier REF to work"
27b126fb-c708-427d-b0a6-ce4747114ac4,"TechnologyService","ACGN-100Q-020T","w_read_account"
bb0c5e42-5fad-4bd9-8ee9-f41d0b824e82,"TechnologyService","ACGN-100Q-030T","w_read_referential"
0b8b76e3-62fa-4527-9f05-2eb4dbaa8e97,"TechnologyService","ACGN-100Q-040T","w_load_CompanyGroup"
1f487986-3cac-4af8-bda2-6400a1c71f48,"TechnologyService","ACGN-100Q-050T","w_load_Company"
"#
$expectedCsv = #"
"ID","Type","Name","Documentation","Source","Target"
"New ID","RealizationRelationship","","","eb214110-2b6a-48b2-ba5a-7c13dc3bba39","a46681e7-19a8-4fc5-b747-09679c15ff26"
"New ID","TriggeringRelationship","","","a46681e7-19a8-4fc5-b747-09679c15ff26","85761a09-1145-4037-a527-66a743def45f"
"New ID","TriggeringRelationship","","","85761a09-1145-4037-a527-66a743def45f","27b126fb-c708-427d-b0a6-ce4747114ac4"
"New ID","TriggeringRelationship","","","27b126fb-c708-427d-b0a6-ce4747114ac4","bb0c5e42-5fad-4bd9-8ee9-f41d0b824e82"
"New ID","TriggeringRelationship","","","bb0c5e42-5fad-4bd9-8ee9-f41d0b824e82","0b8b76e3-62fa-4527-9f05-2eb4dbaa8e97"
"New ID","TriggeringRelationship","","","0b8b76e3-62fa-4527-9f05-2eb4dbaa8e97","1f487986-3cac-4af8-bda2-6400a1c71f48"
"#;
$relations = $inputCsv | ConvertFrom-Csv;
$elements = ConvertTo-ElementItems -Relations $relations;
$outputCsv = ($elements | ConvertTo-Csv -NoTypeInformation) -join "`n";
if( $outputCsv -ne $expectedCsv )
{
throw "output csv doesn't match expected csv";
} else {
write-host "output csv matches expected csv";
}

Powershell - Checking variable for value in foreach - If no value then log other output

I'm having issue with my foreach method. I am checking in the registry whether a good amount of programs are installed. How would I write it to say something is not installed one time versus it saying something's not installed for each key it checks? Now, If I place a ElseIf it executes "PowerBroker not installed." about 16 times. This is due to it checking every key and writing it out for each key it does not find a match to the displayname. How do I go about it checking the key and only writing it out one time if it's not installed?? Thanks!
$UninstallKeys = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
foreach($Key in $UninstallKeys){
if($Key.GetValue("DisplayName") -Match "BeyondTrust"){
$PBW = $Key.GetValue("DisplayName")
$PBWV = $Key.GetValue("DisplayVersion")
if ($PBW) {
$PBW = $PBW, $PBWV
}
else {
$PBW = "PowerBroker not installed."
$installsmissing = "True"
}
}
Give this script a whirl. If I've understood the requirement correctly it should give you what you need.
$displayName = "BeyondTrust"
$uninstallKeys = Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
# Filter the keys down by their display name property
$specificUninstallKeys = $uninstallKeys |
Where-Object {
$_.GetValue("DisplayName") -eq $displayName
}
# Did we find any keys of that name?
if ($specificUninstallKeys) {
Write-Output "Keys found: $($specificUninstallKeys.Length)"
}
else {
Write-Output "Sorry pal, no keys by that name here!"
}
# There may be more than one; hence the loop-y requirement here.
foreach ($specificUninstallKey in $specificUninstallKey) {
Write-Output $displayName
Write-Output $specificUninstallKey.GetValue("DisplayVersion")
}

Powershell - match with Containskey & set value of hashtable don't work

I am working on a script by Richard L. Mueller to disable inactive account in our AD.
Trap {"Error: $_"; Break;}
$D = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$Domain = [ADSI]"LDAP://$D"
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.PageSize = 200
$Searcher.SearchScope = "subtree"
$Searcher.Filter = "(&(objectCategory=person)(objectClass=user))"
$Searcher.PropertiesToLoad.Add("samAccountName") > $Null
$Searcher.PropertiesToLoad.Add("lastLogon") > $Null
$Searcher.PropertiesToLoad.Add("accountExpires") > $Null
# Create hash table of users and their last logon dates.
$arrUsers = #{}
# Enumerate all Domain Controllers.
ForEach ($DC In $D.DomainControllers)
{
$Server = $DC.Name
$Searcher.SearchRoot = "LDAP://$Server/" + $Domain.distinguishedName
$Results = $Searcher.FindAll()
#$Results[100].Properties.item("samAccountName")
#$Results[100].Properties.item("lastlogon")
ForEach ($Result In $Results)
{
$DN = $Result.Properties.Item("samAccountName")
$LL = $Result.Properties.Item("lastLogon")
If ($LL.Count -eq 0)
{
$Last = [DateTime]0
}
Else
{
$Last = [DateTime]$LL.Item(0)
}
If ($Last -eq 0)
{
$LastLogon = $Last.AddYears(1600)
}
Else
{
$LastLogon = $Last.AddYears(1600).ToLocalTime()
}
If ($arrUsers.ContainsKey("$DN"))
{
If ($LastLogon -gt $arrUsers["$DN"])
{
$arrUsers["$DN"] = $LastLogon
}
}
Else
{
$arrUsers.Add("$DN", $LastLogon)
}
}
}
Now I have the most updated LastLogon date of my AD users.
Then I do:
Foreach ($ou in $searchRoot) {
$inactiveUsers += #(Get-QADUser -SearchRoot $ou -Enabled -PasswordNeverExpires:$false -CreatedBefore $creationCutoff -SizeLimit $sizeLimit | Select-Object Name,SamAccountName,LastLogonTimeStamp,Description,passwordneverexpires,canonicalName | Sort-Object Name)
}
I do not use this to disable the ID because LastLogonTimeStamp has a delay being updated from 9-14 days. And with the real last logon date in $arrUsers, I would like to replace LastLogonTimeStamp with it. So I want to match them using the user ID:
Foreach ($inuser in $inactiveUsers) {
If ($arrUsers.ContainsKey("$inuser.samAccountName"))
{
write-host "True"
$inuser.LastLogonTimeStamp = $arrUsers["$inuser.samAccountName"]
$inuser.LastLogonTimeStamp = $inuser.LastLogonTimeStamp.adddays(30)
If ((Get-Date) -gt $inuser.LastLogonTimeStamp)
{
write-host $inuser.samAccountName "should be disabled"
}
Else
{
write-host $inuser.samAccountName "is still active"
}
}
}
Else
{
write-host "False"
}
I have 2 problems here.
First the "If ($arrUsers.ContainsKey("$inuser.samAccountName"))" doesn't seems working. I always get a false result.
Second, to replace the LastLogonTimeStamp using "$inuser.LastLogonTimeStamp = $arrUsers["$inuser.samAccountName"]", my LastLogonTimeStamp become blank.
Could someone able to provide some assistants?
You're not using variable expansion correctly. Object properties aren't expanded, so this
"$inuser.samaccountname"
is actually:
$inuser.ToString() + ".samaccountname"
To expand an expression in a string, you must surround it with $(), e.g.
"$($inuser.samaccountname)"
In your case, however, you don't even need to do that. Leave the quotes out entirely:
$arrusers[$DN]
$arrusers.ContainsKey($inuser.samaccountname)
See the about_Quoting_Rules help topic for details.
I have solved this by assign the samAccountName value to another variable:
$tmpAccountName = $inuser.samAccountName
then
If ($arrUsers.ContainsKey("$tmpAccountName"))
instead of throw the $inuser.samAccountName directly to the checking. Not so sure why it cannot be read directly however at least it is solved now =). Same goes to the problem #2.

PowerShell GUI Output

I'm writing a small PowerShell script, with GUI, that processes the Ping command. Currently I have the script reading input from the user to determine the IP Address / URL to ping, and then displaying the output to a rich-text-box.
However, currently, the command COMPLETES then writes the entire output at once. I want it to display each line of output in real time - so that it appears the same way that it would running the command in the shell.
When the Ping button is pushed, the following function is called (and I think this is where the issue is):
Function FNPing {
$OutputBox.Text = "Please Wait..."
$ping = ping ($InputBox.text)
$OutputBox.Text = ""
foreach ($line in $ping) {
$OutputBox.Appendtext($line+[char]13+[char]10)
}
}
I imagine that the issue can probably be solved in the ForEach statement, I'm just not aware of how to do it! All help is appreciated!
I would try using the test-connection cmdlet. The problem is that ping an external exe so all you are getting out of it is a blob of text when it completes. The only way to get the output of ping while it is running is going to be by using start-process and redirecting the output (this is quite messy).
With test-connection you won't get a pretty summary but the info is all there. For the summary info, measure-object can help. Here's an imitation of ping's output that should get you started:
function fnping {
$outputBox.Text = "Please Wait..."
$count = 4
$results = test-connection $inputbox.Text -count $count | foreach { $outputBox.AppendText("Reply from $($_.ProtocolAddress): bytes=$($_.ReplySize) time=$($_.ResponseTime)ms TTL=$($_.TimeToLive)`r`n"); $_ }
$summary = $results | measure-object -Property ResponseTime -Average -Minimum -Maximum
$lost = $count - $summary.Count
$percentLost = $lost * 100.0 / $count
$outputBox.AppendText("Packets: Sent = $count, Received = $($summary.Count), Lost = $lost ($($percentLost)% loss)`r`n")
$outputBox.AppendText("Minimum = $($summary.Minimum)ms, Maximum = $($summary.Maximum)ms, Average = $($summary.Average)ms`r`n")
}
Edit
Actually I stand corrected. Assigning the result to a variable ($ping) is causing powershell to wait for the output stream to be closed. You can easily do what you want, with foreach-object. Here I use a small helper function to clear the output box before writing the to the box:
function WriteEach-Object() {
param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[object[]]$inputs
)
begin { $outputBox.Text = "" }
process { $inputs | foreach { $outputBox.AppendText($_) } }
end { $outputBox.AppendText("`r`n") }
}
function fnping() {
$outputBox.Text = "Please Wait..."
ping $inputBox.Text | writeeach-object
}
You need to add BEGIN {} PROCESS {} and END {} statements to control how your function works. If no such are given, Powershell assumes that everything is in the END {} statement, processing everything in one go.
Changing into this
Function FNPing {
BEGIN {
}
PROCESS {
$OutputBox.Text = "Please Wait..."
$ping = ping ($InputBox.text)
$OutputBox.Text = ""
foreach ($line in $ping) {
$OutputBox.Appendtext($line+[char]13+[char]10)
}
}
END {
}
}
should do the trick. Alternatively, you can replace Function with Filter, which assumes the code resides in the PROCESS {} block.
For more info: http://technet.microsoft.com/en-us/magazine/hh413265.aspx

Is it possible to include functions only without executing the script?

Say I have MyScript.ps1:
[cmdletbinding()]
param (
[Parameter(Mandatory=$true)]
[string] $MyInput
)
function Show-Input {
param ([string] $Incoming)
Write-Output $Incoming
}
function Save-TheWorld {
#ToDo
}
Write-Host (Show-Input $MyInput)
Is it possible to dot source the functions only somehow? The problem is that if the script above is dot sourced, it executes the whole thing...
Is my best option to use Get-Content and parse out the functions and use Invoke-Expression...? Or is there a way to access PowerShell's parser programmatically? I see this might be possible with PSv3 using [System.Management.Automation.Language.Parser]::ParseInput but this isn't an option because it has to work on PSv2.
The reason why I'm asking is that i'm trying out the Pester PowerShell unit testing framework and the way it runs tests on functions is by dot sourcing the file with the functions in the test fixture. The test fixture looks like this:
MyScript.Tests.ps1
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".")
. "$here\$sut"
Describe "Show-Input" {
It "Verifies input 'Hello' is equal to output 'Hello'" {
$output = Show-Input "Hello"
$output.should.be("Hello")
}
}
Using Doug's Get-Function function you could include the functions this way:
$script = get-item .\myscript.ps1
foreach ($function in (get-function $script))
{
$startline = $function.line - 1
$endline = $startline
$successful = $false
while (! $successful)
{
try {
$partialfunction = ((get-content $script)[$startline..$endline]) -join [environment]::newline
invoke-expression $partialfunction
$successful = $true
}
catch [Exception] { $endline++ }
}
}
Edit: [System.Management.Automation.IncompleteParseException] can be used instead of [Exception] in Powershell V2.
Note -- if you find this answer helpful please upvote jonZ's answer as I wouldn't of been able to come up with this if it weren't for his helpful answer.
I created this function extractor function based on the script #jonZ linked to. This uses [System.Management.Automation.PsParser]::Tokenize to traverse all tokens in the input script and parses out functions into function info objects and returns all function info objects as an array. Each object looks like this:
Start : 99
Stop : 182
StartLine : 7
Name : Show-Input
StopLine : 10
StartColumn : 5
StopColumn : 1
Text : {function Show-Input {, param ([string] $Incoming), Write-Output $Incoming, }}
The text property is a string array and can be written to temporary file and dot sourced in or combined into a string using a newline and imported using Invoke-Expression.
Only the function text is extracted so if a line has multiple statements such as: Get-Process ; function foo () { only the part relevant to the function will be extracted.
function Get-Functions {
param (
[Parameter(Mandatory=$true)]
[System.IO.FileInfo] $File
)
try {
$content = Get-Content $File
$PSTokens = [System.Management.Automation.PsParser]::Tokenize($content, [ref] $null)
$functions = #()
#Traverse tokens.
for ($i = 0; $i -lt $PSTokens.Count; $i++) {
if($PSTokens[$i].Type -eq 'Keyword' -and $PSTokens[$i].Content -eq 'Function' ) {
$fxStart = $PSTokens[$i].Start
$fxStartLine = $PSTokens[$i].StartLine
$fxStartCol = $PSTokens[$i].StartColumn
#Skip to the function name.
while (-not ($PSTokens[$i].Type -eq 'CommandArgument')) {$i++}
$functionName = $PSTokens[$i].Content
#Skip to the start of the function body.
while (-not ($PSTokens[$i].Type -eq 'GroupStart') -and -not ($PSTokens[$i].Content -eq '{')) {$i++ }
#Skip to the closing brace.
$startCount = 1
while ($startCount -gt 0) { $i++
if ($PSTokens[$i].Type -eq 'GroupStart' -and $PSTokens[$i].Content -eq '{') {$startCount++}
if ($PSTokens[$i].Type -eq 'GroupEnd' -and $PSTokens[$i].Content -eq '}') {$startCount--}
}
$fxStop = $PSTokens[$i].Start
$fxStopLine = $PSTokens[$i].StartLine
$fxStopCol = $PSTokens[$i].StartColumn
#Extract function text. Handle 1 line functions.
$fxText = $content[($fxStartLine -1)..($fxStopLine -1)]
$origLine = $fxText[0]
$fxText[0] = $fxText[0].Substring(($fxStartCol -1), $fxText[0].Length - ($fxStartCol -1))
if ($fxText[0] -eq $fxText[-1]) {
$fxText[-1] = $fxText[-1].Substring(0, ($fxStopCol - ($origLine.Length - $fxText[0].Length)))
} else {
$fxText[-1] = $fxText[-1].Substring(0, ($fxStopCol))
}
$fxInfo = New-Object -TypeName PsObject -Property #{
Name = $functionName
Start = $fxStart
StartLine = $fxStartLine
StartColumn = $fxStartCol
Stop = $fxStop
StopLine = $fxStopLine
StopColumn = $fxStopCol
Text = $fxText
}
$functions += $fxInfo
}
}
return $functions
} catch {
throw "Failed in parse file '{0}'. The error was '{1}'." -f $File, $_
}
}
# Dumping to file and dot sourcing:
Get-Functions -File C:\MyScript.ps1 | Select -ExpandProperty Text | Out-File C:\fxs.ps1
. C:\fxs.ps1
Show-Input "hi"
#Or import without dumping to file:
Get-Functions -File C:\MyScript.ps1 | % {
$_.Text -join [Environment]::NewLine | Invoke-Expression
}
Show-Input "hi"