I am trying to wrap a text fixture around some PowerShell code that extends an object with a property. I get an error that appears to be caused by Pester. I have a contrived example below that displays what I am trying to do.
Has anyone succeeded in writing tests on functions that use properties with Pester?
The error I get:
Describing Get-PropertyOfItem
Select-Object : Property cannot be processed because property "should" already exists.
At C:\Repos\ClinicientOps\clinicientops\General\Functions\Get-PropertyOfItem.ps1:4 char:11
+ $files | Select-Object *, #{Name = "TestProperty"; Expression = { $dir.Length}} ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (Windows:PSObject) [Select-Object], PSArgumentException
+ FullyQualifiedErrorId : AlreadyExistingUserSpecifiedPropertyNoExpand,Microsoft.PowerShell.Commands.SelectObjectCommand
My function:
function Get-PropertyOfItem {
$dir = "C:\"
$files = Get-ChildItem $dir
$files | Select-Object *, #{Name = "TestProperty"; Expression = { $dir.Length}} -Last 1
}
My test code:
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".")
. "$here\$sut"
Describe "Get-PropertyOfItem" {
It "does something useful" {
$prop = Get-PropertyOfItem
$prop.TestProperty.should.be(3)
}
}
It appears to be a limitation they are investigating in version 2.
Pester version 2.0.1 has been silently released. You'll have to rewrite your expectation to be
$prop.TestProperty | Should Be 3
It also means that all your other tests will need to migrate to this pipeline form Expectation syntax.
Related
I'm trying to follow the tutorial found here: https://wiki.qt.io/Building_a_static_Qt_for_Windows_using_MinGW
I already have qt 6.3.1 installed, i download the PowerShell script and changed it to:
[CmdletBinding()]
param(
$QtSrcUrl = "https://download.qt.io/official_releases/qt/6.3/6.3.1/single/qt-everywhere-src-6.3.1.zip",
$QtStaticDir = "C:\Qt\Static", # NO TRAILING SLASH
$QtVersion = "6.3.1", #If you change this, you'll need to change the URL above to download as well...
$MingwDir = "",
[switch]$NoPause = $false
)
Im getting this error:
Out-File : Could not find a part of the path 'C:\Qt\Static\6.3.1\mkspecs\win32-g++\qmake.conf'.
At C:\Qt\qt-windows10-static-build.ps1:167 char:6
+ "# | Out-File -Append $File -Encoding Ascii
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OpenError: (:) [Out-File], DirectoryNotFoundException
+ FullyQualifiedErrorId : FileOpenFailure,Microsoft.PowerShell.Commands.OutFileCommand
The folder C:\Qt\Static\6.3.1 is empty, why does the ps script is searching for something inside of it?
I have a script that I can double click and it'll open other scripts as admin. Works with some things but not everything. For one script, it opens the next window and then immediately closes it. For another, I get this error:
At MYPATH\InstallClient.ps1:33 char:78
+ ... tall_x64.msi" -force -recurse -ErrorAction Stop #Cleans out the file ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The string is missing the terminator: ".
At MYPATH\InstallClient.ps1:27 char:31
+ ForEach ($entry in $computers){ #start of foreach loop
+ ~
Missing closing '}' in statement block or type definition.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : TerminatorExpectedAtEndOfString
Below is the script to open a script as an admin:
Function Get-FileName($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = "PS1 (*.ps1)| *.ps1"
$OpenFileDialog.ShowDialog() | Out-Null
$OpenFileDialog.filename
}
$inputfile = Get-FileName "MYPATH\Scripts"
powershell.exe -noprofile -command "&{start-process powershell -ArgumentList '-NoExit -noprofile -file $inputfile' -verb RunAs}"
This is the script that it gives the previous error for while trying to open:
Function Get-FileName($initialDirectory) #Function to choose a file
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = "MSI (*.msi)| *.msi" #type of files that will be available for selection
$OpenFileDialog.ShowDialog() | Out-Null
$OpenFileDialog.filename
}
$inputfile = Get-FileName "MyPath" #Directory that is going to open to select a file from
Function Get-FileName($initialDirectory) #Function to choose a file
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = "CSV (*.csv)| *.csv" #type of files that will be available for selection
$OpenFileDialog.ShowDialog() | Out-Null
$OpenFileDialog.filename
}
$inputfile1 = Get-FileName "MyPath\ServerLists"
$computers = import-csv $inputfile1
ForEach ($entry in $computers){ #start of foreach loop
$computername = $entry.computernames #this saves the single entry under computernames for each entry in csv file
Copy-item $inputfile -container -recurse \\$computername\C$\windows\temp #this copies the msi file that we selected to the computer entry called from the csv file's temp folder
Invoke-Command -Computername $computername –ScriptBlock {Start-process -Wait "C:\windows\temp\ShadowSuiteClientInstall_x64.msi"} | out-null #This starts the msi file that we just copied and waits for the installation to be completed before moving on
If($?){ #If the last command was successful
Echo "Installed ShadowSuiteClientInstall_x64 on $computername."
Remove-Item "\\$computername\C$\windows\temp\ShadowSuiteClientInstall_x64.msi" -force -recurse -ErrorAction Stop #Cleans out the file we copied into the temp folder
}
}
Does anyone have any ideas on why this will open some things fine but give this error for this script and immediately close other scripts without running them? Does anyone have a better way to navigate through scripts and select one to open as admin?
Ok I figured this out. I loaded the script into powershell ISE and I saw that it was compiling it incorrectly. It kept turning the -Scriptblock into an ae symbol instead of the - in front of scriptblock. Weird AF IMO but ok, I fixed it in ISE, which I recommend to anyone struggling with weird compiling errors like this.
I have a solution file that includes some projects inside and I'd like to delete some of them using PowerShell.
The aim is to delete a block of text that contains a string (let's say "abcxyz") starting with "Project" and ends with "EndProject" in the next line (or more than that).
For example:
Project("{1111-2222-FFFF-3333}") = "AutoRun", "..\generate\Infra\generate\generate.csproj", "{999999-UUUUUU-GGGGGG-ABCDEFGH}"
EndProject
Project("{5555-2222-FFFF-3333}") = "SetupSec", "..\generate\Setup.csproj", "{999999-UUUUUU-GGGGGG-ABCDEFGH}"
EndProject
Project("{4444-2222-FFFF-3333}") = "Common.Fyyy", "..\generate\Infra\Common\Common.csproj", "{999999-UUUUUU-GGGGGG-ABCDEFGH}"
EndProject
Project("{9999-2222-FFFF-3333}") = "Command.Console", "..\generate\Path\Console.csproj", "{999999-UUUUUU-GGGGGG-ABCDEFGH}"
EndProject
Project("{7777-2222-FFFF-3333}") = "Infra.GUI", "..\..\generate\GUI.csproj", "{999999-UUUUUU-GGGGGG-ABCDEFGH}"
ProjectSection(ProjectDependencies) = postProject
{AAAA-2222-FFFF-3333} = {999999-UUUUUU-GGGGGG-ABCDEFGH}
EndProjectSection
EndProject
In the example above, I'd like to remove projects contains the string "Infra" in any case.
Is there a simple way of doing it using PowerShell Regex?
(deletion should be done to entire solution file from Project to EndProject)
Thanks,
Shai.
I came here looking for a similar solution. What I did was create this powershell function. It may just be a good jumping off point where you can tailor to your more specific requirements, but it works for me. Enjoy...
function Remove-SourceControl() {
param (
[string]$sourceFilePathname
)
$tempFile = New-TemporaryFile
$filterOn = $false
$fileVersionCorrect = $false
$backupFile = "$sourceFilePathname (backup)"
Copy-Item $sourceFilePathname $backupFile
$rowIndex = 1
Get-Content -Path:$sourceFilePathname | ForEach-Object {
if ($_ -like "*Microsoft Visual Studio Solution File, Format Version 12.00*" ) {
$fileVersionCorrect = $true
}
if ($fileVersionCorrect -eq $true) {
if ($_ -like "*TeamFoundationVersionControl*") { $filterOn = $true }
if (!$filterOn) {
Write-Output $_ | Out-File -FilePath:$tempFile -Append
}
if ($filterOn -and $_ -like "*EndGlobalSection*") { $filterOn = $false }
} else {
Write-Output $_ | Out-File -FilePath:$tempFile -Append
}
$rowIndex++
}
if ($fileVersionCorrect -eq $true) {
Copy-Item $tempFile $sourceFilePathname -Force
}
Remove-Item $tempFile
}
$path = "c:\some-folder-pathname"
Remove-SourceControl "$path\MySoluton.sln"
Assuming your actual file is consistent with the sample data, you can do this without needing to mess with regex at all:
Get-Content $ProjectFile -Delimiter 'EndProject' |
Where-Object {$_ -notlike '*Infra*'} |
Add-Content $NewProjectFile
That will break up the file into separate projects, and then filter out any of them that contain the string "Infra", and write the rest to a new file.
Your question is "is there a simple way in Powershell" and the answer is: No.
Solution files are a nasty old format with various widely-separated parts that depend on each other. It is possible to edit them by hand but it is hard to get right and easy to get wrong.
If you simply want to delete some projects, open the file in Visual Studio, delete the projects, and save it.
If this question is to solve part of a larger problem, you should ask a new question, how to solve that larger problem.
I am trying to work with NETSH from PowerShell. I want see a result from this command such as an object, but netsh returns a string:
netsh wlan show hostednetwork | Get-Member
TypeName: System.String
...
My script must work on system with rather localization, and I can't use -match for parsing a string to an object directly.
How I can solve my trouble?
$netshResult = Invoke-Command -Computername localhost {netsh int tcp show global}
$result = #{}
$netshObject = New-Object psobject -Property #{
ReceiveSideScalingState = $Null
ChimneyOffloadState = $Null
NetDMAState = $Null
}
$netshResult = $netshResult | Select-String : #break into chunks if colon only
$i = 0
while($i -lt $netshResult.Length){
$line = $netshResult[$i]
$line = $line -split(":")
$line[0] = $line[0].trim()
$line[1] = $line[1].trim()
$result.$($line[0]) = $($line[1])
$i++
}
$netshObject.ReceiveSideScalingState = $result.'Receive-Side Scaling State'
$netshObject.ChimneyOffloadState = $result.'Chimney Offload State'
$netshObject.NetDMAState = $result.'NetDMA State'
You got a few alternatives, none of which are nice.
1) Read the netsh output into a string[] and use a custom record parser to create your own object. That is, look at the output on different locales and find out if, say, Hosted newtork settings is always the first header followed by bunch of - characters. If that's the case, assume that next element in array is Mode and so on. This is very error prone, but usually MS command line tools only translate messages, not their order.
2) Look for .Net API for the same information. There is System.Net.NetworkInformation which contains a bunch of connection things. It's a start, though I am not sure if it has info you need.
3) Failing the previous options, use P/Invoke to call native Win32 API. It's a lot of work, so look for pre-existing wrapper libraries before rolling your own.
I recently wrote a cmdlet to parse arbitrary, multi-line text using regular expressions, called ConvertFrom-Text. (Not a great name, if you ask me, but it conforms to the PowerShell naming rules; suggestions are welcome!) So assuming you have that cmdlet, here is one possible solution to your question. (Caveat emptor! The regular expression given was derived from a very small sample of netsh output, so may need some tuning.)
$regex = [regex] '(?ms)(?:^\s*$\s*)?^(?<section>.*?)\s*-+\s*(?<data>.*?)\s*^\s*$'
$result = netsh wlan show hostednetwork | Out-String |
ConvertFrom-Text -pattern $regex -multiline
$result | % {
$dataObj = [PsCustomObject]#{}
$_.Data -split "`r`n" | % {
$element = $_ -split '\s*:\s*'
Add-Member -InputObject $dataObj -MemberType NoteProperty -Name $element[0].Trim() -Value $element[1].Trim()
}
$_.Data = $dataObj # Replace data text with data object
}
$result
On my test system, netsh wlan show hostednetwork returns this:
Hosted network settings
-----------------------
Mode : Allowed
Settings : <Not configured>
Hosted network status
---------------------
Status : Not available
And the output of the $result variable in the code above yields this:
section data
------- ----
Hosted network settings #{Mode=Allowed; Settings=<Not configured>}
Hosted network status #{Status=Not available}
So $result is an array of objects with section and data properties, and the latter is an object with properties defined by the output of the netsh command.
Of course, the above does not get you very far without the ConvertFrom-Text cmdlet. So here is the implementation. (I have copious documentation and examples for it, which will be publicly available once I eventually add it to my open-source PowerShell library.)
filter ConvertFrom-Text
{
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true,Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]$InputObject,
[Parameter(Mandatory=$true,Position=1)]
[regex]$Pattern,
[switch]$RequireAll,
[switch]$Multiline
)
if ($Multiline) {
$dataString = $InputObject -join "`n"
IterateByMatch $dataString $Pattern
}
else {
IterateByLine $InputObject $Pattern
}
}
function IterateByLine([string[]]$data, [regex]$regex)
{
$data | ForEach-Object {
if ($PSItem -match $regex)
{
New-Object PSObject -Property (GetRegexNamedGroups $matches)
}
elseif ($RequireAll) {
throw "invalid line: $_"
}
}
}
function IterateByMatch([string[]]$data, [regex]$regex)
{
$regex.matches($data) | Foreach-Object {
$match = $_
$obj = new-object object
$regex.GetGroupNames() |
Where-Object {$_ -notmatch '^\d+$'} |
Foreach-Object {
Add-Member -InputObject $obj NoteProperty `
$_ $match.groups[$regex.GroupNumberFromName($_)].value
}
$obj
}
}
function Get-RegexNamedGroups($hash)
{
$newHash = #{};
$hash.keys | ? { $_ -notmatch '^\d+$' } | % { $newHash[$_] = $hash[$_] }
$newHash
}
I am trying to write a simple script that stops and then starts a process (Application).
I can stop it fine, but can't find a way to start it again.
The string to start the process should be: "c:\AppFolder\AppName.exe" instance1
my script is:
$appName = "AppName.exe"
$filter = "name like '%"+$appName+"%'"
$result = Get-WmiObject win32_process -Filter $filter
$processid = $result.ProcessId
$command = $result.CommandLine
stop-process $processid
start $command
If I run $result | select * I see that there is an item for CommandLine which is "C:\AppFolder\AppName.exe" instance1
But If I try and do:
$command = $result.CommandLine
stop-process $processid
start $command
I get start-process : This command cannot be executed due to the error: The system cannot find the file specified
But if I manually type into a powershell window start "c:\AppFolder\AppName.exe" instance1 the Application starts fine.
Am I missing something here?
(n.b. it was suggested to me in "powershell v2 - how to get process ID" that I could use
$processid = get-process appName | select -expand id to get the processid, but when I expanded this to get all the items (probably not the correct term?)
in the object I couldn't see an option for CommandLine or similar)
I found the following (but still doesn't work)
$command = Get-WmiObject win32_process -Filter $filter | select -expandproperty CommandLine
write-host $command
This writes "c:\AppFolder\AppName.exe" instance1
start-process $command
But this then results in the following error:
Start-Process : This command cannot be executed due to the error: The system cannot find the file specified
.
At line:11 char:14
+ start-process <<<< $command
+ CategoryInfo : InvalidOperation: (:) [Start-Process], InvalidOperationException
+ FullyQualifiedErrorId : InvalidOperationException,Microsoft.PowerShell.Commands.StartProcessCommand
However, running:
start-process "c:\AppFolder\AppName.exe" instance1
starts the application?
I think I've solved it!! (and it's quite simple really) After much googling. . .
Apparently the start-process cmdlet only accepts a file location.
In order to add an argument (in this case the instance name) I need to use the attribute -ArgumentList
So I need to get the CommandLine item, and split it up, then pass it back in two parts
e.g.
$result = Get-WmiObject win32_process -Filter $filter
$comLine = $result.CommandLine -split"( )"
$comm = $commLine[0]
$inst = $commLine[2]
start-process -FilePath $comm -ArgumentList $inst
And this works as I expected it to.