Idiomatic and Conventional F#
Regex.Matches(solutionText, "([^\"]*csproj)")
|&gt; Seq.cast&lt;Match&gt;
|&gt; Seq.map (fun m -&gt; Path.Combine(solutionDirPath, m.Groups.[1].Value))


Why are you using groups? Since the whole match is a single group, you can remove the parentheses from the expression and then directly use m.Value.

Also, you should probably check that the name ends in .csproj, not just csproj and that it's actually the end of the file name.



let codeFilePaths = 
    projectPaths
    |&gt; Seq.map (fun projPath -&gt; 
        let projectDirPath = Path.GetDirectoryName(projPath)
        let document = XDocument.Load(projPath)
        document.Descendants()
            |&gt; Seq.where (fun desc -&gt; desc.Name.LocalName = "Compile")
            |&gt; Seq.map (fun desc -&gt; Path.Combine(projectDirPath, desc.FirstAttribute.Value)))
        |&gt; Seq.concat



I think the complicated map lambda makes this hard to read, I would instead write it as a local named function. If you don't want to do that, at least make sure to indent your code properly (the first |&gt; Seq.map and the |&gt; Seq.concat should be on the same level).
Instead of the where, you can use the overload of Descendants that accepts a node name (but this means you have to use XML namespaces).
Instead of relying on the first attribute being the right one, I would access the attribute by name.


With all these changes and John Palmer's suggestion about Seq.collect, the code becomes:

let codeFilePaths = 
    let codeFilesForProject projPath = 
        let projectDirPath = Path.GetDirectoryName(projPath)
        let document = XDocument.Load(projPath)
        let ns = XNamespace.Get "http://schemas.microsoft.com/developer/msbuild/2003"
        document.Descendants(ns + "Compile")
            |&gt; Seq.map (fun desc -&gt; Path.Combine(projectDirPath, desc.Attribute(XName.Get "Include").Value))
    projectPaths |&gt; Seq.collect codeFilesForProject