YorSubs
YorSubs

Reputation: 4050

PowerShell, pipeline compatible Select-String function

This is more about my weak understanding of pipelines and functions than anything, so would appreciate help. As an example:

Get-Service | Select-String win   # fails
Get-Service | Out-String -Stream | Select-String win   # works

I've seen explanations on why the Out-String -Stream is required, but it frustrates me a bit, so I want a function oss that does the Out-String -Stream part. My idea is like this:

function oss { Out-String -Stream }
Get-Service | oss | Select-String win

But this fails. Can someone tell me how to make this work, i.e. make the oss function pipeline compatible?

And from that, how do we then do the following (obviously this doesn't work, but I'm really curious how this would be achieved)?

function slx { Out-String -Stream | Select-String <and allow all of the same switches as Select-String> }
Get-Service | slx win

So that, effectively, slx is a variant of Select-String that always does the Out-String -Stream part as well?

Upvotes: 5

Views: 1595

Answers (3)

js2010
js2010

Reputation: 27423

This comes up a lot. Other options. In the case of get-service, both -name and -displayname take wildcards:

get-service *win*
get-service -displayname *win*

Or if wildcards aren't available, but you need to know the property name, which is usually the column heading, in this case displayname:

get-service | where displayname -match win

Sometimes something like this gets some results. It depends on what the string version of the object becomes:

get-service | where { $_ -match 'win' } 

Occasionally it is useful to convert everything to a string and search it. I'm not above doing this. "/i" is case insensitive.

get-service | findstr /i win

Upvotes: 0

mklement0
mklement0

Reputation: 437813

Note: This answer complements marsze's helpful answer.


Windows PowerShell v5.1 and PowerShell [Core] v6+ have an oss function built in, which effectively provides the functionality of Out-String -Stream via a proxy function, as shown in @marsze's answer.

  • As an aside: the code that is auto-generated when you scaffold a proxy function has minor flaws as of PowerShell 7.1; in short: the OutBuffer-related code only applies to server-side proxy functions that need to guard against DoS (denial-of-service) attacks, and the throw statements should be $PSCmdlet.ThrowTerminatingError($_) instead - see GitHub issue #10863.

While this helper function is useful, it shouldn't be required in the first place, because Select-String should implicitly operate on formatted output to begin with:

  • That is, for input that isn't already a string (text), Select-String should search the same richly formatted text representation that you see in the console (terminal).

    • Note: As pointed out in marsze's answer, in programmatic use, for robustness, you should take advantage of PowerShell's object-oriented nature and query properties instead, with Where-Object.
      However, for quick-and-dirty interactive use it can be very convenient to search through the for-display representations.
  • GitHub suggestion #10726 proposes just that, though there seem to be backward-compatibility concerns, even though I think there shouldn't be.


You can adapt the proxy-function approach from marsze's answers to properly implement the slx function you envision, as advanced function Select-StringFormatted, aliased to scs (see rationale below; of course, you can also define slx).

Note:

  • The bulk of the code is the auto-generated declaration of the same parameters that Select-String supports; the actual proxying (wrapping) code via Out-String -Stream is minimal.

  • Alias scs is what Select-String's built-in alias sls should have been, given that the official alias prefix for approved verb Select is sc, not ls; in PowerShell [Core] v6+ you an easily verify that with Get-Verb Select. Since the implicit Out-String -Stream behavior should be Select-String's default behavior anyway, I think it makes sense to pick this alias; if/when Select-String gets fixed, you can redefine scs to point to Select-String.

  • All parameters supported as of v7.0 are declared; if you try to use ones only introduced in 7.0 in earlier versions, namely -Culture, -Raw, and -NoEmphasis, they are ignored and a warning is emitted.

  • Example command (compare to the output from Get-Process | sls service):

    PS> Get-Process | scs service  # scs == Select-StringFormatted
    
        395      16     3728      14656              5664   0 SecurityHealthService                                                                                
        360      11     3864       7696               632   0 services                                                                                             
        173      12     3324       8628              2388   0 VGAuthService                                                                                        
    

Note: The function below is also available as an MIT-licensed Gist, and only the latter will be maintained going forward. Assuming you have looked at the linked code to ensure that it is safe (which I can personally assure you of, but you should always check), you can install it as follows:

irm https://gist.github.com/mklement0/46fea9e6e5ef1a3ceaf681c976cb68e3/raw/Select-StringFormatted.ps1 | iex
Set-Alias scs Select-StringFormatted

function Select-StringFormatted {
  <#
    .SYNOPSIS
    Select-String wrapper command that for non-string input searches the
    *formatted* object representations rather than the often useless
    .ToString() representations.

    All Select-String parameters are supported. See the latter's help for more.

    .EXAMPLE
    Get-Process | Select-StringFormatted pwsh

    Output lines that contain "pwsh" in Get-Process' formatted output.

    .LINK
    Select-String

    #>
  [CmdletBinding(DefaultParameterSetName = 'File', HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=2097119')]
  param(
    [ValidateSet('Ordinal', 'Invariant', 'Current', '', 'af', 'af-NA', 'af-ZA', 'agq', 'agq-CM', 'ak', 'ak-GH', 'am', 'am-ET', 'ar', 'ar-001', 'ar-AE', 'ar-BH', 'ar-DJ', 'ar-DZ', 'ar-EG', 'ar-EH', 'ar-ER', 'ar-IL', 'ar-IQ', 'ar-JO', 'ar-KM', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-MR', 'ar-OM', 'ar-PS', 'ar-QA', 'ar-SA', 'ar-SD', 'ar-SO', 'ar-SS', 'ar-SY', 'ar-TD', 'ar-TN', 'ar-YE', 'arn', 'arn-CL', 'as', 'as-IN', 'asa', 'asa-TZ', 'ast', 'ast-ES', 'az', 'az-Cyrl', 'az-Cyrl-AZ', 'az-Latn', 'az-Latn-AZ', 'ba', 'ba-RU', 'bas', 'bas-CM', 'be', 'be-BY', 'bem', 'bem-ZM', 'bez', 'bez-TZ', 'bg', 'bg-BG', 'bm', 'bm-ML', 'bn', 'bn-BD', 'bn-IN', 'bo', 'bo-CN', 'bo-IN', 'br', 'br-FR', 'brx', 'brx-IN', 'bs', 'bs-Cyrl', 'bs-Cyrl-BA', 'bs-Latn', 'bs-Latn-BA', 'byn', 'byn-ER', 'ca', 'ca-AD', 'ca-ES', 'ca-FR', 'ca-IT', 'ccp', 'ccp-BD', 'ccp-IN', 'ce', 'ce-RU', 'ceb', 'ceb-PH', 'cgg', 'cgg-UG', 'chr', 'chr-US', 'ckb', 'ckb-IQ', 'ckb-IR', 'co', 'co-FR', 'cs', 'cs-CZ', 'cv', 'cv-RU', 'cy', 'cy-GB', 'da', 'da-DK', 'da-GL', 'dav', 'dav-KE', 'de', 'de-AT', 'de-BE', 'de-CH', 'de-DE', 'de-IT', 'de-LI', 'de-LU', 'dje', 'dje-NE', 'dsb', 'dsb-DE', 'dua', 'dua-CM', 'dv', 'dv-MV', 'dyo', 'dyo-SN', 'dz', 'dz-BT', 'ebu', 'ebu-KE', 'ee', 'ee-GH', 'ee-TG', 'el', 'el-CY', 'el-GR', 'en', 'en-001', 'en-150', 'en-AD', 'en-AE', 'en-AG', 'en-AI', 'en-AL', 'en-AR', 'en-AS', 'en-AT', 'en-AU', 'en-BA', 'en-BB', 'en-BD', 'en-BE', 'en-BG', 'en-BI', 'en-BM', 'en-BR', 'en-BS', 'en-BW', 'en-BZ', 'en-CA', 'en-CC', 'en-CH', 'en-CK', 'en-CL', 'en-CM', 'en-CN', 'en-CO', 'en-CX', 'en-CY', 'en-CZ', 'en-DE', 'en-DG', 'en-DK', 'en-DM', 'en-EE', 'en-ER', 'en-ES', 'en-FI', 'en-FJ', 'en-FK', 'en-FM', 'en-FR', 'en-GB', 'en-GD', 'en-GG', 'en-GH', 'en-GI', 'en-GM', 'en-GR', 'en-GU', 'en-GY', 'en-HK', 'en-HR', 'en-HU', 'en-ID', 'en-IE', 'en-IL', 'en-IM', 'en-IN', 'en-IO', 'en-IS', 'en-IT', 'en-JE', 'en-JM', 'en-JP', 'en-KE', 'en-KI', 'en-KN', 'en-KR', 'en-KY', 'en-LC', 'en-LR', 'en-LS', 'en-LT', 'en-LU', 'en-LV', 'en-ME', 'en-MG', 'en-MH', 'en-MM', 'en-MO', 'en-MP', 'en-MS', 'en-MT', 'en-MU', 'en-MV', 'en-MW', 'en-MX', 'en-MY', 'en-NA', 'en-NF', 'en-NG', 'en-NL', 'en-NO', 'en-NR', 'en-NU', 'en-NZ', 'en-PG', 'en-PH', 'en-PK', 'en-PL', 'en-PN', 'en-PR', 'en-PT', 'en-PW', 'en-RO', 'en-RS', 'en-RU', 'en-RW', 'en-SA', 'en-SB', 'en-SC', 'en-SD', 'en-SE', 'en-SG', 'en-SH', 'en-SI', 'en-SK', 'en-SL', 'en-SS', 'en-SX', 'en-SZ', 'en-TC', 'en-TH', 'en-TK', 'en-TO', 'en-TR', 'en-TT', 'en-TV', 'en-TW', 'en-TZ', 'en-UA', 'en-UG', 'en-UM', 'en-US', 'en-US-POSIX', 'en-VC', 'en-VG', 'en-VI', 'en-VU', 'en-WS', 'en-ZA', 'en-ZM', 'en-ZW', 'eo', 'eo-001', 'es', 'es-419', 'es-AG', 'es-AI', 'es-AR', 'es-AW', 'es-BB', 'es-BL', 'es-BM', 'es-BO', 'es-BQ', 'es-BR', 'es-BS', 'es-BZ', 'es-CA', 'es-CL', 'es-CO', 'es-CR', 'es-CU', 'es-CW', 'es-DM', 'es-DO', 'es-EA', 'es-EC', 'es-ES', 'es-FK', 'es-GD', 'es-GF', 'es-GL', 'es-GP', 'es-GQ', 'es-GT', 'es-GY', 'es-HN', 'es-HT', 'es-IC', 'es-KN', 'es-KY', 'es-LC', 'es-MF', 'es-MQ', 'es-MS', 'es-MX', 'es-NI', 'es-PA', 'es-PE', 'es-PH', 'es-PM', 'es-PR', 'es-PY', 'es-SR', 'es-SV', 'es-SX', 'es-TC', 'es-TT', 'es-US', 'es-UY', 'es-VC', 'es-VE', 'es-VG', 'es-VI', 'et', 'et-EE', 'eu', 'eu-ES', 'ewo', 'ewo-CM', 'fa', 'fa-AF', 'fa-IR', 'ff', 'ff-Latn', 'ff-Latn-BF', 'ff-Latn-CM', 'ff-Latn-GH', 'ff-Latn-GM', 'ff-Latn-GN', 'ff-Latn-GW', 'ff-Latn-LR', 'ff-Latn-MR', 'ff-Latn-NE', 'ff-Latn-NG', 'ff-Latn-SL', 'ff-Latn-SN', 'fi', 'fi-FI', 'fil', 'fil-PH', 'fo', 'fo-DK', 'fo-FO', 'fr', 'fr-BE', 'fr-BF', 'fr-BI', 'fr-BJ', 'fr-BL', 'fr-CA', 'fr-CD', 'fr-CF', 'fr-CG', 'fr-CH', 'fr-CI', 'fr-CM', 'fr-DJ', 'fr-DZ', 'fr-FR', 'fr-GA', 'fr-GF', 'fr-GN', 'fr-GP', 'fr-GQ', 'fr-HT', 'fr-KM', 'fr-LU', 'fr-MA', 'fr-MC', 'fr-MF', 'fr-MG', 'fr-ML', 'fr-MQ', 'fr-MR', 'fr-MU', 'fr-NC', 'fr-NE', 'fr-PF', 'fr-PM', 'fr-RE', 'fr-RW', 'fr-SC', 'fr-SN', 'fr-SY', 'fr-TD', 'fr-TG', 'fr-TN', 'fr-VU', 'fr-WF', 'fr-YT', 'fur', 'fur-IT', 'fy', 'fy-NL', 'ga', 'ga-IE', 'gaa', 'gaa-GH', 'gd', 'gd-GB', 'gez', 'gez-ER', 'gez-ET', 'gl', 'gl-ES', 'gn', 'gn-PY', 'gsw', 'gsw-CH', 'gsw-FR', 'gsw-LI', 'gu', 'gu-IN', 'guz', 'guz-KE', 'gv', 'gv-IM', 'ha', 'ha-GH', 'ha-NE', 'ha-NG', 'haw', 'haw-US', 'he', 'he-IL', 'hi', 'hi-IN', 'hi-Latn', 'hr', 'hr-BA', 'hr-HR', 'hsb', 'hsb-DE', 'hu', 'hu-HU', 'hy', 'hy-AM', 'ia', 'ia-001', 'id', 'id-ID', 'ig', 'ig-NG', 'ii', 'ii-CN', 'io', 'io-001', 'is', 'is-IS', 'it', 'it-CH', 'it-IT', 'it-SM', 'it-VA', 'iu', 'iu-CA', 'ja', 'ja-JP', 'jbo', 'jbo-001', 'jgo', 'jgo-CM', 'jmc', 'jmc-TZ', 'jv', 'jv-ID', 'ka', 'ka-GE', 'kab', 'kab-DZ', 'kaj', 'kaj-NG', 'kam', 'kam-KE', 'kcg', 'kcg-NG', 'kde', 'kde-TZ', 'kea', 'kea-CV', 'khq', 'khq-ML', 'ki', 'ki-KE', 'kk', 'kk-KZ', 'kkj', 'kkj-CM', 'kl', 'kl-GL', 'kln', 'kln-KE', 'km', 'km-KH', 'kn', 'kn-IN', 'ko', 'ko-KP', 'ko-KR', 'kok', 'kok-IN', 'kpe', 'kpe-GN', 'kpe-LR', 'ks', 'ks-Arab', 'ks-Arab-IN', 'ks-Aran', 'ks-Aran-IN', 'ks-Deva', 'ks-IN', 'ksb', 'ksb-TZ', 'ksf', 'ksf-CM', 'ksh', 'ksh-DE', 'ku', 'ku-TR', 'kw', 'kw-GB', 'ky', 'ky-KG', 'lag', 'lag-TZ', 'lb', 'lb-LU', 'lg', 'lg-UG', 'lkt', 'lkt-US', 'ln', 'ln-AO', 'ln-CD', 'ln-CF', 'ln-CG', 'lo', 'lo-LA', 'lrc', 'lrc-IQ', 'lrc-IR', 'lt', 'lt-LT', 'lu', 'lu-CD', 'luo', 'luo-KE', 'luy', 'luy-KE', 'lv', 'lv-LV', 'mas', 'mas-KE', 'mas-TZ', 'mer', 'mer-KE', 'mfe', 'mfe-MU', 'mg', 'mg-MG', 'mgh', 'mgh-MZ', 'mgo', 'mgo-CM', 'mi', 'mi-NZ', 'mk', 'mk-MK', 'ml', 'ml-IN', 'mn', 'mn-MN', 'mni', 'mni-Beng', 'mni-Beng-IN', 'mni-Mtei', 'mni-Mtei-IN', 'moh', 'moh-CA', 'mr', 'mr-IN', 'ms', 'ms-Arab', 'ms-Arab-BN', 'ms-Arab-MY', 'ms-BN', 'ms-MY', 'ms-SG', 'mt', 'mt-MT', 'mua', 'mua-CM', 'my', 'my-MM', 'myv', 'myv-RU', 'mzn', 'mzn-IR', 'naq', 'naq-NA', 'nb', 'nb-NO', 'nb-SJ', 'nd', 'nd-ZW', 'nds', 'nds-DE', 'nds-NL', 'ne', 'ne-IN', 'ne-NP', 'nl', 'nl-AW', 'nl-BE', 'nl-BQ', 'nl-CW', 'nl-NL', 'nl-SR', 'nl-SX', 'nmg', 'nmg-CM', 'nn', 'nn-NO', 'nnh', 'nnh-CM', 'nqo', 'nqo-GN', 'nr', 'nr-ZA', 'nso', 'nso-ZA', 'nus', 'nus-SS', 'ny', 'ny-MW', 'nyn', 'nyn-UG', 'oc', 'oc-FR', 'om', 'om-ET', 'om-KE', 'or', 'or-IN', 'os', 'os-GE', 'os-RU', 'pa', 'pa-Arab', 'pa-Arab-PK', 'pa-Aran', 'pa-Aran-PK', 'pa-Guru', 'pa-Guru-IN', 'pl', 'pl-PL', 'ps', 'ps-AF', 'ps-PK', 'pt', 'pt-AO', 'pt-BR', 'pt-CH', 'pt-CV', 'pt-FR', 'pt-GQ', 'pt-GW', 'pt-LU', 'pt-MO', 'pt-MZ', 'pt-PT', 'pt-ST', 'pt-TL', 'qu', 'qu-BO', 'qu-EC', 'qu-PE', 'rm', 'rm-CH', 'rn', 'rn-BI', 'ro', 'ro-MD', 'ro-RO', 'rof', 'rof-TZ', 'ru', 'ru-BY', 'ru-KG', 'ru-KZ', 'ru-MD', 'ru-RU', 'ru-UA', 'rw', 'rw-RW', 'rwk', 'rwk-TZ', 'sa', 'sa-IN', 'sah', 'sah-RU', 'saq', 'saq-KE', 'sat', 'sat-Deva', 'sat-Deva-IN', 'sat-Olck', 'sat-Olck-IN', 'sbp', 'sbp-TZ', 'sc', 'sc-IT', 'scn', 'scn-IT', 'sd', 'sd-Deva', 'sd-PK', 'se', 'se-FI', 'se-NO', 'se-SE', 'seh', 'seh-MZ', 'ses', 'ses-ML', 'sg', 'sg-CF', 'shi', 'shi-Latn', 'shi-Latn-MA', 'shi-Tfng', 'shi-Tfng-MA', 'si', 'si-LK', 'sk', 'sk-SK', 'sl', 'sl-SI', 'smn', 'smn-FI', 'sn', 'sn-ZW', 'so', 'so-DJ', 'so-ET', 'so-KE', 'so-SO', 'sq', 'sq-AL', 'sq-MK', 'sq-XK', 'sr', 'sr-Cyrl', 'sr-Cyrl-BA', 'sr-Cyrl-ME', 'sr-Cyrl-RS', 'sr-Cyrl-XK', 'sr-Latn', 'sr-Latn-BA', 'sr-Latn-ME', 'sr-Latn-RS', 'sr-Latn-XK', 'ss', 'ss-SZ', 'ss-ZA', 'st', 'st-LS', 'st-ZA', 'sv', 'sv-AX', 'sv-FI', 'sv-SE', 'sw', 'sw-CD', 'sw-KE', 'sw-TZ', 'sw-UG', 'syr', 'syr-IQ', 'syr-SY', 'ta', 'ta-IN', 'ta-LK', 'ta-MY', 'ta-SG', 'te', 'te-IN', 'teo', 'teo-KE', 'teo-UG', 'tg', 'tg-TJ', 'th', 'th-TH', 'ti', 'ti-ER', 'ti-ET', 'tig', 'tig-ER', 'tk', 'tk-TM', 'tn', 'tn-BW', 'tn-ZA', 'to', 'to-TO', 'tr', 'tr-CY', 'tr-TR', 'trv', 'trv-TW', 'ts', 'ts-ZA', 'tt', 'tt-RU', 'twq', 'twq-NE', 'tzm', 'tzm-MA', 'ug', 'ug-CN', 'uk', 'uk-UA', 'ur', 'ur-Arab', 'ur-Arab-IN', 'ur-Arab-PK', 'ur-Aran', 'ur-Aran-IN', 'ur-Aran-PK', 'ur-IN', 'ur-PK', 'uz', 'uz-Arab', 'uz-Arab-AF', 'uz-Cyrl', 'uz-Cyrl-UZ', 'uz-Latn', 'uz-Latn-UZ', 'vai', 'vai-Latn', 'vai-Latn-LR', 'vai-Vaii', 'vai-Vaii-LR', 've', 've-ZA', 'vi', 'vi-VN', 'vun', 'vun-TZ', 'wa', 'wa-BE', 'wae', 'wae-CH', 'wal', 'wal-ET', 'wo', 'wo-SN', 'wuu', 'xh', 'xh-ZA', 'xog', 'xog-UG', 'yav', 'yav-CM', 'yi', 'yi-001', 'yo', 'yo-BJ', 'yo-NG', 'yue', 'yue-Hans', 'yue-Hans-CN', 'yue-Hant', 'yue-Hant-HK', 'zgh', 'zgh-MA', 'zh', 'zh-Hans', 'zh-Hans-CN', 'zh-Hans-HK', 'zh-Hans-MO', 'zh-Hans-SG', 'zh-Hant', 'zh-Hant-CN', 'zh-Hant-HK', 'zh-Hant-MO', 'zh-Hant-TW', 'zu', 'zu-ZA')]
    [ValidateNotNull()]
    [string]
    ${Culture},
    
    [Parameter(ParameterSetName = 'ObjectRaw', Mandatory, ValueFromPipeline)]
    [Parameter(ParameterSetName = 'Object', Mandatory, ValueFromPipeline)]
    [AllowEmptyString()]
    [AllowNull()]
    [psobject]
    ${InputObject},
    
    [Parameter(Mandatory, Position = 0)]
    [string[]]
    ${Pattern},
    
    [Parameter(ParameterSetName = 'FileRaw', Mandatory, Position = 1, ValueFromPipelineByPropertyName)]
    [Parameter(ParameterSetName = 'File', Mandatory, Position = 1, ValueFromPipelineByPropertyName)]
    [string[]]
    ${Path},
    
    [Parameter(ParameterSetName = 'LiteralFileRaw', Mandatory, ValueFromPipelineByPropertyName)]
    [Parameter(ParameterSetName = 'LiteralFile', Mandatory, ValueFromPipelineByPropertyName)]
    [Alias('PSPath', 'LP')]
    [string[]]
    ${LiteralPath},
    
    [Parameter(ParameterSetName = 'LiteralFileRaw', Mandatory)]
    [Parameter(ParameterSetName = 'FileRaw', Mandatory)]
    [Parameter(ParameterSetName = 'ObjectRaw', Mandatory)]
    [switch]
    ${Raw},
    
    [switch]
    ${SimpleMatch},
    
    [switch]
    ${CaseSensitive},
    
    [Parameter(ParameterSetName = 'File')]
    [Parameter(ParameterSetName = 'Object')]
    [Parameter(ParameterSetName = 'LiteralFile')]
    [switch]
    ${Quiet},
    
    [switch]
    ${List},
    
    [switch]
    ${NoEmphasis},
    
    [ValidateNotNullOrEmpty()]
    [string[]]
    ${Include},
    
    [ValidateNotNullOrEmpty()]
    [string[]]
    ${Exclude},
    
    [switch]
    ${NotMatch},
    
    [switch]
    ${AllMatches},
    
    [ValidateNotNullOrEmpty()]
    [System.Text.Encoding]
    ${Encoding},
    
    [ValidateRange(0, 2147483647)]
    [ValidateNotNullOrEmpty()]
    [ValidateCount(1, 2)]
    [int[]]
    ${Context})
    
  begin {
    # Ignore parameters that are only supported in v7+
    if ($PSVersionTable.PSVersion.Major -lt 7) {
      ('Culture', 'Raw', 'NoEmphasis').ForEach( {
          if ($PSBoundParameters.ContainsKey($_)) {
            $null = $PSBoundParameters.Remove($_)
            Write-Warning "Ignoring parameter $_, because it is only supported in PowerShell 7.0 or above."
          }
        })
    }
    # Set up the steppable pipeline.
    try {
      $commandToWrap = { Out-String -Stream | Select-String @PSBoundParameters }
      $steppablePipeline = $commandToWrap.GetSteppablePipeline($myInvocation.CommandOrigin)
      $steppablePipeline.Begin($PSCmdlet)
    }
    catch {
      $PSCmdlet.ThrowTerminatingError($_)
    }
  }
    
  process {
    $steppablePipeline.Process($_)
  }
    
  end {
    $steppablePipeline.End()
  }
    
}

Upvotes: 2

marsze
marsze

Reputation: 17055

I appreciate that you want to learn, so I will not just give you a working solution, but also explain a few things.

It seems like your you have a basic understanding of how functions and the pipeline work. If you want to learn more about it have a look at about_Pipelines and about_Functions (and related help topics), or let me know what in particular you want to know in the comments.

One thing you need to understand though if you are new to Powershell, is that it it's object-based, not text-based. It's objects that are passed through the pipeline, and you should work with them as such, and not convert them to text (unless for file output).

The documentation about Select-String states specifically, that it is meant for finding text in strings and files. But this is not what you are trying to do here. As I said, work with objects, not text. So omit the Out-String. The proper command to filter objects in a pipeline is Where-Object or one of its aliases where or ?.

Example:

Get-Service | where { $_.DisplayName -like *win* }
# in newer Powershell versions, this shorter syntax is also possible:
Get-Service | where DisplayName -like *win*

But as you specifically asked about making a function "pipeline aware", this can be done in several ways. But your problem is not only getting input from the pipeline, but also they way you pass it to Out-String, because the output will change if you pass only one item at a time.

Basically you have to pass the input all at once. So, the simplest way to achieve this in your case is using the $Input automatic variable:

 function oss { $Input | Out-String -Stream }

If you need to process one pipeline item at a time, there are more ways to get pipeline input. For example an advanced function:

function example { 
    process {
        $_ # current item
    }
}

You can also use the parameter attribute to bind the pipeline input to a function parameter:

function example {
    param(
        [Parameter(ValueFromPipeline = $true)]
        $InputObject
    )
    process {
        $InputObject # current item
    }
}

The last way I can think of is using a filter, which is a special kind of function which is specifically designed to perform an operation on each pipeline element:

filter example {
    $_ # current item
}

To learn more about it I recommend a google search which will quickly provide you with useful articles such as Build a PowerShell function for the pipeline or Incorporating Pipelined Input into PowerShell Functions.

If you truly want to build a method that is basically a wrapper for another cmdlet like Out-String or Select-String with all the same switches, you should have a look at proxy functions. But I think this would be overkill for what you're trying to do:

function oss {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $true)]
        [psobject]$InputObject
    )
    begin
    {
        try {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
            {
                $PSBoundParameters['OutBuffer'] = 1
            }
            # set -Stream switch always
            $PSBoundParameters["Stream"] = $true
            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Out-String', [System.Management.Automation.CommandTypes]::Cmdlet)
            $scriptCmd = {& $wrappedCmd @PSBoundParameters }
            $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
            $steppablePipeline.Begin($PSCmdlet)
        } catch {
            throw
        }
    }
    process
    {
        try {
            $steppablePipeline.Process($_)
        } catch {
            throw
        }
    }
    end
    {
        try {
            $steppablePipeline.End()
        } catch {
            throw
        }
    }
}

Upvotes: 6

Related Questions